diff --git a/.github-actions/maven/github-package-registry-settings.xml b/.github-actions/maven/github-package-registry-settings.xml new file mode 100644 index 000000000..593660a0e --- /dev/null +++ b/.github-actions/maven/github-package-registry-settings.xml @@ -0,0 +1,22 @@ + + + + + github-package-registry + ${env.GITHUB_PACKAGE_REGISTRY_DEPLOYER} + ${env.GITHUB_PACKAGE_REGISTRY_TOKEN} + + + + + + import-env-code-signing-credentials + + gpg + ${env.CODE_SIGNING_GPG_KEY_ID} + ${env.CODE_SIGNING_GPG_KEY_PASSPHRASE} + + + + diff --git a/.github-actions/maven/sonatype-ossrh-settings.xml b/.github-actions/maven/sonatype-ossrh-settings.xml new file mode 100644 index 000000000..680538615 --- /dev/null +++ b/.github-actions/maven/sonatype-ossrh-settings.xml @@ -0,0 +1,22 @@ + + + + + sonatype-ossrh + ${env.SONATYPE_OSSRH_DEPLOYER} + ${env.SONATYPE_OSSRH_TOKEN} + + + + + + import-env-code-signing-credentials + + gpg + ${env.CODE_SIGNING_GPG_KEY_ID} + ${env.CODE_SIGNING_GPG_KEY_PASSPHRASE} + + + + diff --git a/.github-actions/scripts/get_version.sh b/.github-actions/scripts/get_version.sh new file mode 100755 index 000000000..34f468a65 --- /dev/null +++ b/.github-actions/scripts/get_version.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +# This should be called from repository root + +# shellcheck disable=SC2016 +# this is intentional as it is the value passed to Maven +mvn -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec --quiet diff --git a/.github-actions/scripts/maven_deploy.sh b/.github-actions/scripts/maven_deploy.sh new file mode 100755 index 000000000..9c1c7f3b2 --- /dev/null +++ b/.github-actions/scripts/maven_deploy.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# This should be called from repository root + +if [[ "$(./.github-actions/scripts/get_version.sh)" == *-SNAPSHOT ]]; then + if [[ $1 == release ]]; then + >&2 echo "Cannot deploy in release mode when version is snapshot" + exit 1; + fi +else + if [[ $1 != release ]]; then + >&2 echo "Cannot deploy in non-release mode when version is not snapshot" + exit 1; + fi; +fi + + +# Valid deployment modes: +# - sonatype-ossrh +# - github-package-registry +function deploy() { + if [[ $1 != sonatype-ossrh && $1 != github-package-registry ]]; then + echo "Unknown deployment target: $1" + return 1 + fi + + maven_profiles=build-extras,sign-artifacts,import-env-code-signing-credentials,"$1"-deployment + if [[ $2 == release && $1 == sonatype-ossrh ]]; then + maven_profiles="${maven_profiles},automatic-central-release" + fi + echo "Using maven profiles: [${maven_profiles}]" + + mvn deploy --settings ./.github-actions/maven/"$1"-settings.xml --activate-profiles "$maven_profiles" -B -V +} + +deploy sonatype-ossrh "$1" +deploy github-package-registry "$1" \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..0ff5f53c6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,25 @@ +version: 2 +updates: + - package-ecosystem: maven + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + target-branch: development + reviewers: + - JarvisCraft + labels: + - dependencies + - automatic + + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + target-branch: development + reviewers: + - JarvisCraft + labels: + - dependencies + - automatic diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml new file mode 100644 index 000000000..92808d162 --- /dev/null +++ b/.github/workflows/deploy-release.yml @@ -0,0 +1,40 @@ +name: Deploy release + +on: + push: + tags: [ v* ] + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3.3.0 + + - name: Set up JDK 8 + uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: '8' + + - name: Cache Maven local repository + uses: actions/cache@v3.2.2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-maven- + + - name: Import GPG signing key + run: | + echo -e "${{ secrets.CODE_SIGNING_GPG_PRIVATE_KEY }}" | gpg --batch --import + gpg --list-secret-keys --keyid-format LONG + + - name: Deploy snapshot + env: + CODE_SIGNING_GPG_KEY_ID: ${{ secrets.CODE_SIGNING_GPG_KEY_ID }} + CODE_SIGNING_GPG_KEY_PASSPHRASE: ${{ secrets.CODE_SIGNING_GPG_KEY_PASSPHRASE }} + SONATYPE_OSSRH_DEPLOYER: ${{ secrets.SONATYPE_OSSRH_DEPLOYER }} + SONATYPE_OSSRH_TOKEN: ${{ secrets.SONATYPE_OSSRH_TOKEN }} + GITHUB_PACKAGE_REGISTRY_DEPLOYER: JarvisCraft + GITHUB_PACKAGE_REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./.github-actions/scripts/maven_deploy.sh release diff --git a/.github/workflows/deploy-snapshot.yml b/.github/workflows/deploy-snapshot.yml new file mode 100644 index 000000000..a280a7178 --- /dev/null +++ b/.github/workflows/deploy-snapshot.yml @@ -0,0 +1,41 @@ +name: Deploy snapshot + +on: + push: + branches: [ development ] + workflow_dispatch: {} + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3.3.0 + + - name: Set up JDK 8 + uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: '8' + + - name: Cache Maven local repository + uses: actions/cache@v3.2.2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-maven- + + - name: Import GPG signing key + run: | + echo -e "${{ secrets.CODE_SIGNING_GPG_PRIVATE_KEY }}" | gpg --batch --import + gpg --list-secret-keys --keyid-format LONG + + - name: Deploy snapshot + env: + CODE_SIGNING_GPG_KEY_ID: ${{ secrets.CODE_SIGNING_GPG_KEY_ID }} + CODE_SIGNING_GPG_KEY_PASSPHRASE: ${{ secrets.CODE_SIGNING_GPG_KEY_PASSPHRASE }} + SONATYPE_OSSRH_DEPLOYER: ${{ secrets.SONATYPE_OSSRH_DEPLOYER }} + SONATYPE_OSSRH_TOKEN: ${{ secrets.SONATYPE_OSSRH_TOKEN }} + GITHUB_PACKAGE_REGISTRY_DEPLOYER: JarvisCraft + GITHUB_PACKAGE_REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./.github-actions/scripts/maven_deploy.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..8cc1d1823 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: Test + +# Only test (without creating JARs) pull-requests +on: [ pull_request, workflow_dispatch ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3.3.0 + + - name: Set up JDK 8 + uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: '8' + + - name: Cache Maven local repository + uses: actions/cache@v3.2.2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-maven- + + - name: Build with Maven + run: mvn -B test diff --git a/.gitignore b/.gitignore index 2eb6eb700..c58db5faf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ - -# Created by https://www.gitignore.io/api/maven,intellij+all +# Created by https://www.toptal.com/developers/gitignore/api/intellij+all,maven,java +# Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all,maven,java ### Intellij+all ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff @@ -32,9 +32,14 @@ # When using Gradle or Maven with auto-import, you should exclude module files, # since they will be recreated, and may cause churn. Uncomment if using # auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml # .idea/modules.xml # .idea/*.iml # .idea/modules +# *.iml +# *.ipr # CMake cmake-build-*/ @@ -82,6 +87,34 @@ modules.xml .idea/misc.xml *.ipr +# Sonarlint plugin +.idea/sonarlint + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + ### Maven ### target/ pom.xml.tag @@ -92,10 +125,7 @@ release.properties dependency-reduced-pom.xml buildNumber.properties .mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar .mvn/wrapper/maven-wrapper.jar - -# End of https://www.gitignore.io/api/maven,intellij+all - -### Security ### -*.asc \ No newline at end of file +# End of https://www.toptal.com/developers/gitignore/api/intellij+all,maven,java diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 000000000..1e8f2a8e8 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,25 @@ +pull_request_rules: + - name: Automatic merge for labelled pull requests + conditions: + - label=merge when ready + actions: + merge: + method: merge + strict: smart+fasttrack + + - name: Automatic merge for Dependabot pull requests + conditions: + - author~=^dependabot(|-preview)\[bot\]$ + - base=development + actions: + merge: + method: merge + strict: smart+fasttrack + + - name: Remove Mergify temporary labels + conditions: + - merged + actions: + label: + remove: + - merge when ready diff --git a/.mergifyio.yml b/.mergifyio.yml new file mode 100644 index 000000000..1e8f2a8e8 --- /dev/null +++ b/.mergifyio.yml @@ -0,0 +1,25 @@ +pull_request_rules: + - name: Automatic merge for labelled pull requests + conditions: + - label=merge when ready + actions: + merge: + method: merge + strict: smart+fasttrack + + - name: Automatic merge for Dependabot pull requests + conditions: + - author~=^dependabot(|-preview)\[bot\]$ + - base=development + actions: + merge: + method: merge + strict: smart+fasttrack + + - name: Remove Mergify temporary labels + conditions: + - merged + actions: + label: + remove: + - merge when ready diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8458c6719..000000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -# Programming language -language: java - -### Maven basic build ### -# Build project, installing to local repo (skip testing and javadoc generation (if those exist)) -install: mvn clean install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -# Test project -script: mvn test -B -V - -# Cached directories -cache: - directories: - - ~/.m2/repository - -# After success scripts -after_success: -# Give required permissions to used shell scripts -- chmod +x ./deploy.sh -- chmod +x ./project-version.sh -- chmod +x ./.travis/deploy.sh -# Deploy to OSSRH repository if possible -- ./deploy.sh \ No newline at end of file diff --git a/.travis/.mvn/settings.xml b/.travis/.mvn/settings.xml deleted file mode 100644 index c122c0aef..000000000 --- a/.travis/.mvn/settings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - ossrh - ${env.OSSRH_JIRA_USERNAME} - ${env.OSSRH_JIRA_PASSWORD} - - - - - - ossrh-env-credentials - - gpg - ${env.GPG_KEY_NAME} - ${env.GPG_KEY_PASSPHRASE} - - - - \ No newline at end of file diff --git a/.travis/deploy.sh b/.travis/deploy.sh deleted file mode 100644 index 9ec0b6160..000000000 --- a/.travis/deploy.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -echo 'Decrypting encryption key' -openssl aes-256-cbc -pass pass:"$CODESIGNING_ASC_ENC_PASS" \ --in .travis/gpg/codesigning.asc.enc -out .travis/gpg/codesigning.asc -d -echo 'Decrypted encryption key' - -echo 'Importing encryption key' -gpg --fast-import .travis/gpg/codesigning.asc -echo 'Imported encryption key' - -echo 'Deploying artifacts' -# Generate source and javadocs, sign binaries, deploy to Sonatype using credentials from env. -mvn deploy -P build-extras,sign,ossrh-env-credentials,ossrh-deploy --settings .travis/.mvn/settings.xml -echo 'Deployed artifacts' \ No newline at end of file diff --git a/.travis/gpg/codesigning.asc.enc b/.travis/gpg/codesigning.asc.enc deleted file mode 100644 index 00d3c833f..000000000 Binary files a/.travis/gpg/codesigning.asc.enc and /dev/null differ diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkUtil.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkUtil.java deleted file mode 100644 index 1d161ed0e..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkUtil.java +++ /dev/null @@ -1,70 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.chunk; - -import lombok.NonNull; -import lombok.experimental.UtilityClass; -import org.bukkit.Chunk; -import org.bukkit.World; - -/** - * - * @apiNote chunks are (by default) returned as a single {@link long} as the limit of chunk at non-Y-axis is 3750000 - * where the first 32-most significant stand for X coordinate and the last 32 bits stand for Z coordinate - * - * @see General info about chinks - */ -@UtilityClass -public class ChunkUtil { - - /** - * Returns a single {@link long} value storing chunk data for X- and Z-axises. - * - * @param x X coordinate of a chunk - * @param z Z coordinate of a chunk - * @return chunk treated as {@link long} - */ - public long chunk(final int x, final int z) { - return ((long) x << 32) | ((long) z & 0xFFFFFFFFL); - } - - /** - * Returns the X coordinate value from a long-serialized chunk - * - * @param longChunk chunk data treated as {@link long} - * @return X coordinate of a chunk - */ - public int chunkX(final long longChunk) { - return (int) (longChunk >> 32); - } - - /** - * Returns the Z coordinate value from a long-serialized chunk - * - * @param longChunk chunk data treated as long - * @return Z coordinate of a chunk - */ - public int chunkZ(final long longChunk) { - return (int) (longChunk); - } - - /** - * Gets the chunk by location in a world. - * - * @param x X coordinate of the location - * @param z Y coordinate of the location - * @return chunk location treated as {@link long} - */ - public long chunkByLocation(final long x, final long z) { - return chunk((int) (x >> 4), (int) (z >> 4)); - } - - /** - * Gets the chunk in the world specified from a {@link long} chunk representation. - * - * @param world world to get chunk from - * @param chunk chunk treated as {@link long} - * @return specified chunk of the world - */ - public Chunk getChunk(@NonNull final World world, final long chunk) { - return world.getChunkAt(chunkX(chunk), chunkZ(chunk)); - } -} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/LocationUtil.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/LocationUtil.java deleted file mode 100644 index 71b3bb83c..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/LocationUtil.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util; - -import lombok.NonNull; -import lombok.experimental.UtilityClass; -import org.bukkit.Location; -import org.bukkit.block.BlockFace; - -@UtilityClass -public class LocationUtil { - - public double getDistanceSquared(final double dx, final double dy, final double dz) { - return dx * dx + dy * dy + dz * dz; - } - - public double getDistanceSquared(final double x1, final double y1, final double z1, - final double x2, final double y2, final double z2) { - return getDistanceSquared(x2 - x1, y2 - y1, z2 - z1); - } - - public double getDistanceSquared(final Location location1, final Location location2) { - return getDistanceSquared( - location2.getX() - location1.getX(), - location2.getY() - location1.getY(), - location2.getZ() - location1.getZ() - ); - } - - public Location nearestLocation(@NonNull final Location location, @NonNull final BlockFace blockFace) { - return location.add(blockFace.getModX(), blockFace.getModY(), blockFace.getModZ()); - } -} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/ObjectUtil.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/ObjectUtil.java deleted file mode 100644 index 606c4c5d0..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/ObjectUtil.java +++ /dev/null @@ -1,244 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util; - -import lombok.NonNull; -import lombok.experimental.UtilityClass; -import lombok.val; -import ru.progrm_jarvis.minecraft.commons.util.function.UncheckedFunction; -import ru.progrm_jarvis.minecraft.commons.util.function.UncheckedSupplier; - -import java.util.Optional; - -/** - * Utilities for common object operations. - */ -@UtilityClass -public class ObjectUtil { - - /** - * Returns the first nonnull value of specified variants or {@code null} if none found. - * - * @param variants variants which may be nonnull - * @param type of value - * @return first nonnull value found or {@code null} if none - */ - @SafeVarargs - public T nonNull(final T... variants) { - for (val variant : variants) if (variant != null) return variant; - - return null; - } - - /** - * Returns the first nonnull value of specified variants or {@code null} if none found. - * - * @param variants variant suppliers whose values may be null - * @param type of value - * @return first nonnull value found or {@code null} if none - */ - @SafeVarargs - public T nonNull(final UncheckedSupplier... variants) { - for (val variant : variants) { - val value = variant.get(); - if (value != null) return value; - } - - return null; - } - - /** - * Returns the first nonnull value of specified variants wrapped in {@link Optional} or empty if none found. - * - * @param variants variants which may be nonnull - * @param type of value - * @return {@link Optional} containing first nonnull value found or empty if none - */ - @SafeVarargs - public Optional optionalNonNull(final T... variants) { - for (val variant : variants) if (variant != null) return Optional.of(variant); - - return Optional.empty(); - } - - /** - * Returns the first nonnull value of specified variants wrapped in {@link Optional} or empty if none found. - * - * @param variants variant suppliers whose values may be null - * @param type of value - * @return {@link Optional} containing first nonnull value found or empty if none - */ - @SafeVarargs - public Optional optionalNonNull(final UncheckedSupplier... variants) { - for (val variant : variants) { - val value = variant.get(); - if (value != null) return Optional.of(value); - } - - return Optional.empty(); - } - - /** - * Returns the first nonnull value of specified variants or throws {@link NullPointerException} if none found. - * - * @param variants variants which may be nonnull - * @param type of value - * @return first nonnull value found - * @throws NullPointerException if none of the variants specified is nonnull - */ - @SafeVarargs - public T nonNullOrThrow(final T... variants) throws NullPointerException { - for (val variant : variants) if (variant != null) return variant; - - throw new NullPointerException("No nonnull value found among variants"); - } - - /** - * Returns the first nonnull value of specified variants or throws {@link NullPointerException} if none found. - * - * @param variants variant suppliers whose values may be null - * @param type of value - * @return first nonnull value found - * @throws NullPointerException if none of the variants specified is nonnull - */ - @SafeVarargs - public T nonNullOrThrow(final UncheckedSupplier... variants) throws NullPointerException { - for (val variant : variants) { - val value = variant.get(); - if (value != null) return value; - } - - throw new NullPointerException("No nonnull value found among variants"); - } - - /** - * Maps (transforms) the value specified using the mapping function. - * This may come in handy in case of initializing fields with expressions which have checked exceptions. - * - * @param value value to map - * @param mappingFunction function to map the value to the required type - * @param type of source value - * @param type of resulting value - * @return mapped (transformed) value - */ - public R map(final T value, @NonNull final UncheckedFunction mappingFunction) { - return mappingFunction.apply(value); - } - - /** - * Returns the first nonnull value of specified variants or {@code null} if none found - * mapped using function specified. - * - * @param mappingFunction function to map the value to the required type - * @param variants variants which may be nonnull - * @param type of source value - * @param type of resulting value - * @return first nonnull value found or {@code null} if none found mapped using mapping function - */ - @SafeVarargs - public R mapNonNull(@NonNull final UncheckedFunction mappingFunction, - final T... variants) { - for (val variant : variants) if (variant != null) return mappingFunction.apply(variant); - - return mappingFunction.apply(null); - } - - /** - * Returns the first nonnull value of specified variants or {@code null} if none found - * mapped using function specified. - * - * @param mappingFunction function to map the value to the required type - * @param variants variant suppliers whose values may be null - * @param type of source value - * @param type of resulting value - * @return first nonnull value found or {@code null} if none found mapped using mapping function - */ - @SafeVarargs - public R mapNonNull(@NonNull final UncheckedFunction mappingFunction, - final UncheckedSupplier... variants) { - for (val variant : variants) { - val value = variant.get(); - if (value != null) return mappingFunction.apply(value); - } - - return mappingFunction.apply(null); - } - - /** - * Returns the first nonnull value of specified variants mapped using function specified - * or {@code null} if none found. - * - * @param mappingFunction function to map the value to the required type - * @param variants variants which may be nonnull - * @param type of source value - * @param type of resulting value - * @return first nonnull value found mapped using mapping function or {@code null} if none found - */ - @SafeVarargs - public R mapOnlyNonNull(@NonNull final UncheckedFunction mappingFunction, - final T... variants) { - for (val variant : variants) if (variant != null) return mappingFunction.apply(variant); - - return null; - } - - /** - * Returns the first nonnull value of specified variants mapped using function specified - * or {@code null} if none found. - * - * @param mappingFunction function to map the value to the required type - * @param variants variant suppliers whose values may be null - * @param type of source value - * @param type of resulting value - * @return first nonnull value found mapped using mapping function or {@code null} if none found - */ - @SafeVarargs - public R mapOnlyNonNull(@NonNull final UncheckedFunction mappingFunction, - final UncheckedSupplier... variants) { - for (val variant : variants) { - val value = variant.get(); - if (value != null) return mappingFunction.apply(value); - } - - return null; - } - - /** - * Returns the first nonnull value of specified variants mapped using function specified - * or throws {@link NullPointerException} if none found. - * - * @param mappingFunction function to map the value to the required type - * @param variants variants which may be nonnull - * @param type of source value - * @param type of resulting value - * @return first nonnull value found mapped using mapping function - * @throws NullPointerException if none of the variants specified is nonnull - */ - @SafeVarargs - public R mapNonNullOrThrow(@NonNull final UncheckedFunction mappingFunction, - final T... variants) throws NullPointerException { - for (val variant : variants) if (variant != null) return mappingFunction.apply(variant); - - throw new NullPointerException("No nonnull value found among variants"); - } - - /** - * Returns the first nonnull value of specified variants mapped using function specified - * or throws {@link NullPointerException} if none found. - * - * @param mappingFunction function to map the value to the required type - * @param variants variant suppliers whose values may be null - * @param type of source value - * @param type of resulting value - * @return first nonnull value found mapped using mapping function - * @throws NullPointerException if none of the variants specified is nonnull - */ - @SafeVarargs - public R mapNonNullOrThrow(@NonNull final UncheckedFunction mappingFunction, - final UncheckedSupplier... variants) throws NullPointerException { - for (val variant : variants) { - val value = variant.get(); - if (value != null) return mappingFunction.apply(value); - } - - throw new NullPointerException("No nonnull value found among variants"); - } -} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentCollectionWrapper.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentCollectionWrapper.java deleted file mode 100644 index 5b10ec159..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentCollectionWrapper.java +++ /dev/null @@ -1,202 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util.concurrent; - - -import lombok.NonNull; - -import javax.annotation.Nonnull; -import java.util.Collection; -import java.util.Iterator; -import java.util.Spliterator; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.stream.Stream; - -public class ConcurrentCollectionWrapper> - extends ConcurrentWrapper implements Collection { - - public ConcurrentCollectionWrapper(@NonNull final T wrapped) { - super(wrapped); - } - - @Override - public int size() { - readLock.lock(); - try { - return wrapped.size(); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean isEmpty() { - readLock.lock(); - try { - return wrapped.isEmpty(); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean contains(final Object o) { - readLock.lock(); - try { - return wrapped.contains(o); - } finally { - readLock.unlock(); - } - } - - @Override - @Nonnull - public Iterator iterator() { - readLock.lock(); - try { - return wrapped.iterator(); - } finally { - readLock.unlock(); - } - } - - @Override - public void forEach(@NonNull final Consumer action) { - readLock.lock(); - try { - wrapped.forEach(action); - } finally { - readLock.unlock(); - } - } - - @Override - @Nonnull public Object[] toArray() { - readLock.lock(); - try { - return wrapped.toArray(); - } finally { - readLock.unlock(); - } - } - - @Override - @Nonnull public R[] toArray(@NonNull final R[] a) { - readLock.lock(); - try { - //noinspection SuspiciousToArrayCall - return wrapped.toArray(a); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean add(final E e) { - writeLock.lock(); - try { - return wrapped.add(e); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean remove(final Object o) { - writeLock.lock(); - try { - return wrapped.remove(o); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean containsAll(@NonNull final Collection c) { - readLock.lock(); - try { - return wrapped.containsAll(c); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean addAll(@NonNull final Collection c) { - writeLock.lock(); - try { - return wrapped.addAll(c); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean retainAll(@NonNull final Collection c) { - writeLock.lock(); - try { - return wrapped.retainAll(c); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean removeAll(@NonNull final Collection c) { - writeLock.lock(); - try { - return wrapped.removeAll(c); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean removeIf(@NonNull final Predicate filter) { - writeLock.lock(); - try { - return wrapped.removeIf(filter); - } finally { - writeLock.unlock(); - } - } - - @Override - public void clear() { - writeLock.lock(); - try { - wrapped.clear(); - } finally { - writeLock.unlock(); - } - } - - @Override - public Spliterator spliterator() { - readLock.lock(); - try { - return wrapped.spliterator(); - } finally { - readLock.unlock(); - } - } - - @Override - public Stream stream() { - readLock.lock(); - try { - return wrapped.stream(); - } finally { - readLock.unlock(); - } - } - - @Override - public Stream parallelStream() { - readLock.lock(); - try { - return wrapped.parallelStream(); - } finally { - readLock.unlock(); - } - } -} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentCollections.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentCollections.java deleted file mode 100644 index 1f188298f..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentCollections.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util.concurrent; - -import lombok.NonNull; -import lombok.experimental.UtilityClass; - -import java.util.*; -import java.util.concurrent.locks.ReadWriteLock; - -/** - * An utility for creating concurrent {@link Collection} wrappers. - * - * @implNote concurrent wrappers delegate all operations to source collections - * yet performing precondition-checks ans using {@link ReadWriteLock}s - */ -@UtilityClass -public class ConcurrentCollections { - - public Collection concurrentCollection(@NonNull final Collection collection) { - return new ConcurrentCollectionWrapper<>(collection); - } - - public List concurrentList(@NonNull final List list) { - return new ConcurrentListWrapper<>(list); - } - - public Set concurrentSet(@NonNull final Set set) { - return new ConcurrentSetWrapper<>(set); - } - - public Set concurrentSetFromMap(@NonNull final Map map) { - return new ConcurrentSetFromMapWrapper<>(map); - } - - public Queue concurrentQueue(@NonNull final Queue set) { - return new ConcurrentQueueWrapper<>(set); - } - - public Deque concurrentDeque(@NonNull final Deque set) { - return new ConcurrentDequeWrapper<>(set); - } - - public Map concurrentMap(@NonNull final Map map) { - return new ConcurrentMapWrapper<>(map); - } -} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentDequeWrapper.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentDequeWrapper.java deleted file mode 100644 index bb24d5d70..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentDequeWrapper.java +++ /dev/null @@ -1,235 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util.concurrent; - -import lombok.NonNull; - -import javax.annotation.Nonnull; -import java.util.Deque; -import java.util.Iterator; - -public class ConcurrentDequeWrapper> - extends ConcurrentCollectionWrapper implements Deque { - - public ConcurrentDequeWrapper(@NonNull final T wrapped) { - super(wrapped); - } - - @Override - public void addFirst(final E e) { - writeLock.lock(); - try { - wrapped.addFirst(e); - } finally { - writeLock.unlock(); - } - } - - @Override - public void addLast(final E e) { - writeLock.lock(); - try { - wrapped.addLast(e); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean offerFirst(final E e) { - writeLock.lock(); - try { - return wrapped.offerFirst(e); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean offerLast(final E e) { - writeLock.lock(); - try { - return wrapped.offerLast(e); - } finally { - writeLock.unlock(); - } - } - - @Override - public E removeFirst() { - writeLock.lock(); - try { - return wrapped.removeFirst(); - } finally { - writeLock.unlock(); - } - } - - @Override - public E removeLast() { - writeLock.lock(); - try { - return wrapped.removeLast(); - } finally { - writeLock.unlock(); - } - } - - @Override - public E pollFirst() { - writeLock.lock(); - try { - return wrapped.pollFirst(); - } finally { - writeLock.unlock(); - } - } - - @Override - public E pollLast() { - writeLock.lock(); - try { - return wrapped.pollLast(); - } finally { - writeLock.unlock(); - } - } - - @Override - public E getFirst() { - readLock.lock(); - try { - return wrapped.getFirst(); - } finally { - readLock.unlock(); - } - } - - @Override - public E getLast() { - readLock.lock(); - try { - return wrapped.getLast(); - } finally { - readLock.unlock(); - } - } - - @Override - public E peekFirst() { - readLock.lock(); - try { - return wrapped.peekFirst(); - } finally { - readLock.unlock(); - } - } - - @Override - public E peekLast() { - readLock.lock(); - try { - return wrapped.peekLast(); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean removeFirstOccurrence(final Object o) { - writeLock.lock(); - try { - return wrapped.removeFirstOccurrence(o); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean removeLastOccurrence(final Object o) { - writeLock.lock(); - try { - return wrapped.removeLastOccurrence(o); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean offer(final E e) { - writeLock.lock(); - try { - return wrapped.offer(e); - } finally { - writeLock.unlock(); - } - } - - @Override - public E remove() { - writeLock.lock(); - try { - return wrapped.remove(); - } finally { - writeLock.unlock(); - } - } - - @Override - public E poll() { - writeLock.lock(); - try { - return wrapped.poll(); - } finally { - writeLock.unlock(); - } - } - - @Override - public E element() { - readLock.lock(); - try { - return wrapped.element(); - } finally { - readLock.unlock(); - } - } - - @Override - public E peek() { - readLock.lock(); - try { - return wrapped.peek(); - } finally { - readLock.unlock(); - } - } - - @Override - public void push(final E e) { - writeLock.lock(); - try { - wrapped.push(e); - } finally { - writeLock.unlock(); - } - } - - @Override - public E pop() { - writeLock.lock(); - try { - return wrapped.pop(); - } finally { - writeLock.unlock(); - } - } - - @Override - @Nonnull public Iterator descendingIterator() { - readLock.lock(); - try { - return wrapped.descendingIterator(); - } finally { - readLock.unlock(); - } - } -} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentListWrapper.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentListWrapper.java deleted file mode 100644 index 1dcdb79c3..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentListWrapper.java +++ /dev/null @@ -1,155 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util.concurrent; - -import lombok.NonNull; - -import javax.annotation.Nonnull; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; -import java.util.ListIterator; -import java.util.function.UnaryOperator; - -public class ConcurrentListWrapper> extends ConcurrentCollectionWrapper implements List { - - public ConcurrentListWrapper(@NonNull final T wrapped) { - super(wrapped); - } - - @Override - public boolean addAll(final int index, @Nonnull final Collection c) { - if (index < 0) throw new IndexOutOfBoundsException("index should be positive"); - - writeLock.lock(); - try { - return wrapped.addAll(index, c); - } finally { - writeLock.unlock(); - } - } - - @Override - public void replaceAll(@Nonnull final UnaryOperator operator) { - writeLock.lock(); - try { - wrapped.replaceAll(operator); - } finally { - writeLock.unlock(); - } - } - - @Override - public void sort(@Nonnull final Comparator c) { - writeLock.lock(); - try { - wrapped.sort(c); - } finally { - writeLock.unlock(); - } - } - - @Override - public E get(final int index) { - if (index < 0) throw new IndexOutOfBoundsException("index should be positive"); - - readLock.lock(); - try { - return wrapped.get(index); - } finally { - readLock.unlock(); - } - } - - @Override - public E set(final int index, final E element) { - if (index < 0) throw new IndexOutOfBoundsException("index should be positive"); - - writeLock.lock(); - try { - return wrapped.set(index, element); - } finally { - writeLock.unlock(); - } - } - - @Override - public void add(final int index, final E element) { - if (index < 0) throw new IndexOutOfBoundsException("index should be positive"); - - writeLock.lock(); - try { - wrapped.add(index, element); - } finally { - writeLock.unlock(); - } - } - - @Override - public E remove(final int index) { - if (index < 0) throw new IndexOutOfBoundsException("index should be positive"); - - writeLock.lock(); - try { - return wrapped.remove(index); - } finally { - writeLock.unlock(); - } - } - - @Override - public int indexOf(final Object o) { - readLock.lock(); - try { - return wrapped.indexOf(o); - } finally { - readLock.unlock(); - } - } - - @Override - public int lastIndexOf(final Object o) { - readLock.lock(); - try { - return wrapped.lastIndexOf(o); - } finally { - readLock.unlock(); - } - } - - @Override - @Nonnull - public ListIterator listIterator() { - readLock.lock(); - try { - return wrapped.listIterator(); - } finally { - readLock.unlock(); - } - } - - @Override - @Nonnull public ListIterator listIterator(final int index) { - if (index < 0) throw new IndexOutOfBoundsException("index should be positive"); - - readLock.lock(); - try { - return wrapped.listIterator(index); - } finally { - readLock.unlock(); - } - } - - @Override - @Nonnull public List subList(final int fromIndex, final int toIndex) { - if (fromIndex < 0) throw new IndexOutOfBoundsException("fromIndex should be positive"); - if (toIndex < fromIndex) throw new IndexOutOfBoundsException( - "toIndex should be greater than or equal to fromIndex" - ); - - readLock.lock(); - try { - return wrapped.subList(fromIndex, toIndex); - } finally { - readLock.unlock(); - } - } -} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentMapWrapper.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentMapWrapper.java deleted file mode 100644 index 2bbb2f87d..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentMapWrapper.java +++ /dev/null @@ -1,256 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util.concurrent; - - -import lombok.AccessLevel; -import lombok.NonNull; -import lombok.experimental.FieldDefaults; - -import javax.annotation.Nonnull; -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Function; - -@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) -public class ConcurrentMapWrapper> - extends ConcurrentWrapper implements Map { - - public ConcurrentMapWrapper(@NonNull final T wrapped) { - super(wrapped); - } - - @Override - public int size() { - readLock.lock(); - try { - return wrapped.size(); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean isEmpty() { - readLock.lock(); - try { - return wrapped.isEmpty(); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean containsKey(final Object key) { - readLock.lock(); - try { - return wrapped.containsKey(key); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean containsValue(final Object value) { - readLock.lock(); - try { - return wrapped.containsValue(value); - } finally { - readLock.unlock(); - } - } - - @Override - public V get(final Object key) { - readLock.lock(); - try { - return wrapped.get(key); - } finally { - readLock.unlock(); - } - } - - @Override - public V put(final K key, final V value) { - writeLock.lock(); - try { - return wrapped.put(key, value); - } finally { - writeLock.unlock(); - } - } - - @Override - public V remove(final Object key) { - writeLock.lock(); - try { - return wrapped.remove(key); - } finally { - writeLock.unlock(); - } - } - - @Override - public void putAll(@NonNull final Map m) { - writeLock.lock(); - try { - wrapped.putAll(m); - } finally { - writeLock.unlock(); - } - } - - @Override - public void clear() { - writeLock.lock(); - try { - wrapped.clear(); - } finally { - writeLock.unlock(); - } - } - - @Override - @Nonnull - public Set keySet() { - readLock.lock(); - try { - return wrapped.keySet(); - } finally { - readLock.unlock(); - } - } - - @Override - @Nonnull public Collection values() { - readLock.lock(); - try { - return wrapped.values(); - } finally { - readLock.unlock(); - } - } - - @Override - @Nonnull public Set> entrySet() { - readLock.lock(); - try { - return wrapped.entrySet(); - } finally { - readLock.unlock(); - } - } - - @Override - public V getOrDefault(final Object key, final V defaultValue) { - readLock.lock(); - try { - return wrapped.getOrDefault(key, defaultValue); - } finally { - readLock.unlock(); - } - } - - @Override - public void forEach(@NonNull final BiConsumer action) { - readLock.lock(); - try { - wrapped.forEach(action); - } finally { - readLock.unlock(); - } - } - - @Override - public void replaceAll(@NonNull final BiFunction function) { - writeLock.lock(); - try { - wrapped.replaceAll(function); - } finally { - writeLock.unlock(); - } - } - - @Override - public V putIfAbsent(final K key, final V value) { - writeLock.lock(); - try { - return wrapped.putIfAbsent(key, value); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean remove(final Object key, final Object value) { - writeLock.lock(); - try { - return wrapped.remove(key, value); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean replace(final K key, final V oldValue, final V newValue) { - writeLock.lock(); - try { - return wrapped.replace(key, oldValue, newValue); - } finally { - writeLock.unlock(); - } - } - - @Override - public V replace(final K key, final V value) { - writeLock.lock(); - try { - return wrapped.replace(key, value); - } finally { - writeLock.unlock(); - } - } - - @Override - public V computeIfAbsent(final K key, @NonNull final Function mappingFunction) { - writeLock.lock(); - try { - return wrapped.computeIfAbsent(key, mappingFunction); - } finally { - writeLock.unlock(); - } - } - - @Override - public V computeIfPresent(final K key, - @NonNull final BiFunction remappingFunction) { - writeLock.lock(); - try { - return wrapped.computeIfPresent(key, remappingFunction); - } finally { - writeLock.unlock(); - } - } - - @Override - public V compute(final K key, @NonNull final BiFunction remappingFunction) { - writeLock.lock(); - try { - return wrapped.compute(key, remappingFunction); - } finally { - writeLock.unlock(); - } - } - - @Override - public V merge(final K key, @NonNull final V value, - @NonNull final BiFunction remappingFunction) { - writeLock.lock(); - try { - return wrapped.merge(key, value, remappingFunction); - } finally { - writeLock.unlock(); - } - } -} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentQueueWrapper.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentQueueWrapper.java deleted file mode 100644 index 01b4fa1b7..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentQueueWrapper.java +++ /dev/null @@ -1,63 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util.concurrent; - -import lombok.NonNull; - -import java.util.Queue; - -public class ConcurrentQueueWrapper> - extends ConcurrentCollectionWrapper implements Queue { - - public ConcurrentQueueWrapper(@NonNull final T wrapped) { - super(wrapped); - } - - @Override - public boolean offer(final E e) { - writeLock.lock(); - try { - return wrapped.offer(e); - } finally { - writeLock.unlock(); - } - } - - @Override - public E remove() { - writeLock.lock(); - try { - return wrapped.remove(); - } finally { - writeLock.unlock(); - } - } - - @Override - public E poll() { - writeLock.lock(); - try { - return wrapped.poll(); - } finally { - writeLock.unlock(); - } - } - - @Override - public E element() { - readLock.lock(); - try { - return wrapped.element(); - } finally { - readLock.unlock(); - } - } - - @Override - public E peek() { - readLock.lock(); - try { - return wrapped.peek(); - } finally { - readLock.unlock(); - } - } -} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentSetFromMapWrapper.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentSetFromMapWrapper.java deleted file mode 100644 index a3fa4549b..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentSetFromMapWrapper.java +++ /dev/null @@ -1,207 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util.concurrent; - -import lombok.AccessLevel; -import lombok.NonNull; -import lombok.experimental.FieldDefaults; - -import javax.annotation.Nonnull; -import java.util.*; -import java.util.function.*; -import java.util.stream.Stream; - -@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) -public class ConcurrentSetFromMapWrapper> - extends ConcurrentWrapper> implements Set { - - // map used as set backend - T map; - - public ConcurrentSetFromMapWrapper(@NonNull final T wrapped) { - super(wrapped.keySet()); - - this.map = wrapped; - } - - @Override - public int size() { - readLock.lock(); - try { - return map.size(); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean isEmpty() { - readLock.lock(); - try { - return map.isEmpty(); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean contains(final Object o) { - readLock.lock(); - try { - //noinspection SuspiciousMethodCalls - return map.containsKey(o); - } finally { - readLock.unlock(); - } - } - - @Override - @Nonnull - public Iterator iterator() { - readLock.lock(); - try { - return wrapped.iterator(); - } finally { - readLock.unlock(); - } - } - - @Override - public void forEach(@NonNull final Consumer action) { - readLock.lock(); - try { - wrapped.forEach(action); - } finally { - readLock.unlock(); - } - } - - @Override - @Nonnull public Object[] toArray() { - readLock.lock(); - try { - return wrapped.toArray(); - } finally { - readLock.unlock(); - } - } - - @Override - @Nonnull public R[] toArray(@NonNull final R[] a) { - readLock.lock(); - try { - //noinspection SuspiciousToArrayCall - return wrapped.toArray(a); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean add(final E e) { - writeLock.lock(); - try { - return map.put(e, true) == null; - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean remove(final Object o) { - writeLock.lock(); - try { - return map.remove(o) != null; - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean containsAll(@NonNull final Collection c) { - readLock.lock(); - try { - return wrapped.containsAll(c); - } finally { - readLock.unlock(); - } - } - - @Override - public boolean addAll(@NonNull final Collection c) { - writeLock.lock(); - try { - return wrapped.addAll(c); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean retainAll(@NonNull final Collection c) { - writeLock.lock(); - try { - return wrapped.retainAll(c); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean removeAll(@NonNull final Collection c) { - writeLock.lock(); - try { - return wrapped.removeAll(c); - } finally { - writeLock.unlock(); - } - } - - @Override - public boolean removeIf(@NonNull final Predicate filter) { - writeLock.lock(); - try { - return wrapped.removeIf(filter); - } finally { - writeLock.unlock(); - } - } - - @Override - public void clear() { - writeLock.lock(); - try { - map.clear(); - } finally { - writeLock.unlock(); - } - } - - @Override - public Spliterator spliterator() { - readLock.lock(); - try { - return wrapped.spliterator(); - } finally { - readLock.unlock(); - } - } - - @Override - public Stream stream() { - readLock.lock(); - try { - return wrapped.stream(); - } finally { - readLock.unlock(); - } - } - - @Override - public Stream parallelStream() { - readLock.lock(); - try { - return wrapped.parallelStream(); - } finally { - readLock.unlock(); - } - } -} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentSetWrapper.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentSetWrapper.java deleted file mode 100644 index 2ef5eccad..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentSetWrapper.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util.concurrent; - -import lombok.NonNull; - -import java.util.Set; - -public class ConcurrentSetWrapper> - extends ConcurrentCollectionWrapper implements Set { - - public ConcurrentSetWrapper(@NonNull final T wrapped) { - super(wrapped); - } -} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentWrapper.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentWrapper.java deleted file mode 100644 index 5b518c7f3..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/concurrent/ConcurrentWrapper.java +++ /dev/null @@ -1,63 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util.concurrent; - - -import lombok.AccessLevel; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.experimental.FieldDefaults; - -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * Base for all concurrent wrappers. - * - * @param type of wrapped value - */ -@RequiredArgsConstructor -@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) -public class ConcurrentWrapper { - - @NonNull final T wrapped; - - ReadWriteLock lock = new ReentrantReadWriteLock(); - Lock readLock = lock.readLock(); - Lock writeLock = lock.writeLock(); - - /** - * {@inheritDoc} - * - * @implNote this method is not concurrent because if modification happens - * then the result of its call is anyway irrelevant - * @implNote simply calls to {@link #wrapped}'s {@link T#equals(Object)} method - * as it provides mostly symmetric logic - */ - @Override - @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") - public boolean equals(final Object obj) { - return wrapped.equals(obj); - } - - /** - * {@inheritDoc} - * - * @implNote this method is not concurrent because if modification happens - * then the result of its call is anyway irrelevant - * @implNote simply calls to {@link #wrapped}'s {@link T#hashCode()} method - * as it provides a logically unique value - */ - @Override - public int hashCode() { - return wrapped.hashCode(); - } - - /** - * {@inheritDoc} - * @implNote simply adds Concurrent prefix to {@link #wrapped} {@link T#toString()} call result - */ - @Override - public String toString() { - return "Concurrent" + wrapped.toString(); - } -} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/function/UncheckedFunction.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/function/UncheckedFunction.java deleted file mode 100644 index 747e90bbe..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/function/UncheckedFunction.java +++ /dev/null @@ -1,40 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util.function; - -import lombok.NonNull; -import lombok.SneakyThrows; - -import java.util.function.Function; - -/** - * Function which allows having checked exceptions in its method body. - * - * @param {@inheritDoc} - * @param {@inheritDoc} - */ -@FunctionalInterface -public interface UncheckedFunction extends Function { - - /** - * Applies this function to the given argument allowing any checked exceptions in method body. - * - * @param t the function argument - * @return the function result - */ - R operate(T t) throws Throwable; - - @Override - @SneakyThrows - default R apply(T t) { - return operate(t); - } - - @Override - default UncheckedFunction compose(@NonNull Function before) { - return v -> apply(before.apply(v)); - } - - @Override - default UncheckedFunction andThen(@NonNull final Function after) { - return t -> after.apply(apply(t)); - } -} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/function/UncheckedSupplier.java b/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/function/UncheckedSupplier.java deleted file mode 100644 index 58da51bd2..000000000 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/function/UncheckedSupplier.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util.function; - -import lombok.SneakyThrows; - -import java.util.function.Supplier; - -/** - * Supplier which allows having checked exceptions in its method body. - * - * @param {@inheritDoc} - */ -public interface UncheckedSupplier extends Supplier { - - /** - * Gets a result allowing any checked exceptions in method body. - * - * @return a result - */ - T supply() throws Throwable; - - @Override - @SneakyThrows - default T get() { - return supply(); - } -} diff --git a/commons/src/test/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkUtilTest.java b/commons/src/test/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkUtilTest.java deleted file mode 100644 index a8e244da4..000000000 --- a/commons/src/test/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkUtilTest.java +++ /dev/null @@ -1,174 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.chunk; - -import lombok.val; -import org.bukkit.World; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static ru.progrm_jarvis.minecraft.commons.chunk.ChunkUtil.*; - -class ChunkUtilTest { - - @Test - void testChunkAsLong() { - assertEquals(1, chunkX(chunk(1, 0))); - assertEquals(0, chunkZ(chunk(1, 0))); - - assertEquals(0, chunkX(chunk(0, 1))); - assertEquals(1, chunkZ(chunk(0, 1))); - - assertEquals(-1, chunkX(chunk(-1, 0))); - assertEquals(0, chunkZ(chunk(-1, 0))); - - assertEquals(0, chunkX(chunk(0, -1))); - assertEquals(-1, chunkZ(chunk(0, -1))); - - assertEquals(1, chunkX(chunk(1, 255))); - assertEquals(255, chunkZ(chunk(1, 255))); - - assertEquals(255, chunkX(chunk(255, 1))); - assertEquals(1, chunkZ(chunk(255, 1))); - - assertEquals(-1, chunkX(chunk(-1, 255))); - assertEquals(255, chunkZ(chunk(-1, 255))); - - assertEquals(255, chunkX(chunk(255, -1))); - assertEquals(-1, chunkZ(chunk(255, -1))); - - assertEquals(1, chunkX(chunk(1, -255))); - assertEquals(-255, chunkZ(chunk(1, -255))); - - assertEquals(-255, chunkX(chunk(-255, 1))); - assertEquals(1, chunkZ(chunk(-255, 1))); - - assertEquals(-1, chunkX(chunk(-1, -255))); - assertEquals(-255, chunkZ(chunk(-1, -255))); - - assertEquals(-255, chunkX(chunk(-255, -1))); - assertEquals(-1, chunkZ(chunk(-255, -1))); - } - - @Test - void testChunkByLocation() { - long chunk; - - chunk = chunkByLocation(0, 0); - assertEquals(0, chunkX(chunk)); - assertEquals(0, chunkZ(chunk)); - - chunk = chunkByLocation(15, 15); - assertEquals(0, chunkX(chunk)); - assertEquals(0, chunkZ(chunk)); - - chunk = chunkByLocation(16, 16); - assertEquals(1, chunkX(chunk)); - assertEquals(1, chunkZ(chunk)); - - chunk = chunkByLocation(31, 31); - assertEquals(1, chunkX(chunk)); - assertEquals(1, chunkZ(chunk)); - - chunk = chunkByLocation(32, 32); - assertEquals(2, chunkX(chunk)); - assertEquals(2, chunkZ(chunk)); - - chunk = chunkByLocation(47, 47); - assertEquals(2, chunkX(chunk)); - assertEquals(2, chunkZ(chunk)); - - chunk = chunkByLocation(-1, -1); - assertEquals(-1, chunkX(chunk)); - assertEquals(-1, chunkZ(chunk)); - - chunk = chunkByLocation(-16, -16); - assertEquals(-1, chunkX(chunk)); - assertEquals(-1, chunkZ(chunk)); - - chunk = chunkByLocation(-17, -17); - assertEquals(-2, chunkX(chunk)); - assertEquals(-2, chunkZ(chunk)); - - chunk = chunkByLocation(-32, -32); - assertEquals(-2, chunkX(chunk)); - assertEquals(-2, chunkZ(chunk)); - - chunk = chunkByLocation(-33, -33); - assertEquals(-3, chunkX(chunk)); - assertEquals(-3, chunkZ(chunk)); - - chunk = chunkByLocation(-48, -48); - assertEquals(-3, chunkX(chunk)); - assertEquals(-3, chunkZ(chunk)); - - chunk = chunkByLocation(0, 15); - assertEquals(0, chunkX(chunk)); - assertEquals(0, chunkZ(chunk)); - - chunk = chunkByLocation(-1, 15); - assertEquals(-1, chunkX(chunk)); - assertEquals(0, chunkZ(chunk)); - - chunk = chunkByLocation(0, 15); - assertEquals(0, chunkX(chunk)); - assertEquals(0, chunkZ(chunk)); - - chunk = chunkByLocation(-31, 31); - assertEquals(-2, chunkX(chunk)); - assertEquals(1, chunkZ(chunk)); - - chunk = chunkByLocation(64, -32); - assertEquals(4, chunkX(chunk)); - assertEquals(-2, chunkZ(chunk)); - - chunk = chunkByLocation(10, 20); - assertEquals(0, chunkX(chunk)); - assertEquals(1, chunkZ(chunk)); - - chunk = chunkByLocation(-10, 20); - assertEquals(-1, chunkX(chunk)); - assertEquals(1, chunkZ(chunk)); - - chunk = chunkByLocation(10, -20); - assertEquals(0, chunkX(chunk)); - assertEquals(-2, chunkZ(chunk)); - - chunk = chunkByLocation(-10, -20); - assertEquals(-1, chunkX(chunk)); - assertEquals(-2, chunkZ(chunk)); - } - - @Test - void testGetChunkFromWorld() { - val world = mock(World.class); - - getChunk(world, chunk(0, 0)); - verify(world).getChunkAt(ArgumentMatchers.eq(0), ArgumentMatchers.eq(0)); - - getChunk(world, chunk(1, 0)); - verify(world).getChunkAt(ArgumentMatchers.eq(1), ArgumentMatchers.eq(0)); - - getChunk(world, chunk(0, 1)); - verify(world).getChunkAt(ArgumentMatchers.eq(0), ArgumentMatchers.eq(1)); - - getChunk(world, chunk(-1, 0)); - verify(world).getChunkAt(ArgumentMatchers.eq(-1), ArgumentMatchers.eq(0)); - - getChunk(world, chunk(0, -1)); - verify(world).getChunkAt(ArgumentMatchers.eq(0), ArgumentMatchers.eq(-1)); - - getChunk(world, chunk(25, 10)); - verify(world).getChunkAt(ArgumentMatchers.eq(25), ArgumentMatchers.eq(10)); - - getChunk(world, chunk(-14, 8)); - verify(world).getChunkAt(ArgumentMatchers.eq(-14), ArgumentMatchers.eq(8)); - - getChunk(world, chunk(23, -9)); - verify(world).getChunkAt(ArgumentMatchers.eq(23), ArgumentMatchers.eq(-9)); - - getChunk(world, chunk(-6, -22)); - verify(world).getChunkAt(ArgumentMatchers.eq(-6), ArgumentMatchers.eq(-22)); - } -} \ No newline at end of file diff --git a/commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/ObjectUtilTest.java b/commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/ObjectUtilTest.java deleted file mode 100644 index 4537ccac5..000000000 --- a/commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/ObjectUtilTest.java +++ /dev/null @@ -1,123 +0,0 @@ -package ru.progrm_jarvis.minecraft.commons.util; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.Objects; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static ru.progrm_jarvis.minecraft.commons.util.ObjectUtil.*; - -class ObjectUtilTest { - - @Test - void testNonNull() { - assertEquals("foo", nonNull("foo")); - assertEquals("foo", nonNull("foo", null)); - assertEquals("foo", nonNull(null, "foo", null)); - assertEquals("foo", nonNull(null, "foo")); - assertEquals("foo", nonNull("foo", "bar")); - assertEquals("foo", nonNull("foo", null, "bar")); - assertEquals("foo", nonNull(null, "foo", null, "bar")); - assertEquals("foo", nonNull(null, "foo", "bar")); - assertNull(nonNull((Object) null)); - assertNull(nonNull((Object) null, null)); - assertNull(nonNull((Object) null, null, null)); - } - - @Test - void testOptionalNonNull() { - assertEquals(Optional.of("foo"), optionalNonNull("foo")); - assertEquals(Optional.of("foo"), optionalNonNull("foo", null)); - assertEquals(Optional.of("foo"), optionalNonNull(null, "foo", null)); - assertEquals(Optional.of("foo"), optionalNonNull(null, "foo")); - assertEquals(Optional.of("foo"), optionalNonNull("foo", "bar")); - assertEquals(Optional.of("foo"), optionalNonNull("foo", null, "bar")); - assertEquals(Optional.of("foo"), optionalNonNull(null, "foo", null, "bar")); - assertEquals(Optional.of("foo"), optionalNonNull(null, "foo", "bar")); - assertEquals(Optional.empty(), optionalNonNull((Object) null)); - assertEquals(Optional.empty(), optionalNonNull((Object) null, null)); - assertEquals(Optional.empty(), optionalNonNull((Object) null, null, null)); - } - - @Test - void testNonNullOrThrow() { - assertEquals("foo", nonNullOrThrow("foo")); - assertEquals("foo", nonNullOrThrow("foo", null)); - assertEquals("foo", nonNullOrThrow(null, "foo", null)); - assertEquals("foo", nonNullOrThrow(null, "foo")); - assertEquals("foo", nonNullOrThrow("foo", "bar")); - assertEquals("foo", nonNullOrThrow("foo", null, "bar")); - assertEquals("foo", nonNullOrThrow(null, "foo", null, "bar")); - assertEquals("foo", nonNullOrThrow(null, "foo", "bar")); - assertThrows(NullPointerException.class, () -> nonNullOrThrow((Object) null)); - assertThrows(NullPointerException.class, () -> nonNullOrThrow((Object) null, null)); - assertThrows(NullPointerException.class, () -> nonNullOrThrow((Object) null, null, null)); - } - - @Test - void testMap() { - assertEquals("f", ObjectUtil.map("foo", t -> t.substring(0, 1))); - assertEquals("1", ObjectUtil.map(1, t -> Integer.toString(t))); - assertNull(ObjectUtil.map(123, t -> null)); - assertThrows(NullPointerException.class, () -> ObjectUtil.map(null, t -> { - if (t == null) throw new NullPointerException(); - return "nonnull"; - })); - assertThrows(NullPointerException.class, () -> ObjectUtil.map(null, t -> { - throw new NullPointerException(); - })); - assertThrows(IOException.class, () -> ObjectUtil.map(null, t -> { - throw new IOException(); - })); - } - - @Test - void testMapNonNull() { - assertEquals("f", mapNonNull(t -> t.substring(0, 1), "foo")); - assertEquals("f", mapNonNull(t -> t.substring(0, 1), "foo")); - assertEquals("f", mapNonNull(t -> t.substring(0, 1), "foo", null)); - assertEquals("f", mapNonNull(t -> t.substring(0, 1), null, "foo", null)); - assertEquals("f", mapNonNull(t -> t.substring(0, 1), null, "foo")); - assertEquals("f", mapNonNull(t -> t.substring(0, 1), "foo", "bar")); - assertEquals("f", mapNonNull(t -> t.substring(0, 1), "foo", null, "bar")); - assertEquals("f", mapNonNull(t -> t.substring(0, 1), null, "foo", null, "bar")); - assertEquals("f", mapNonNull(t -> t.substring(0, 1), null, "foo", "bar")); - assertEquals("+", mapNonNull(t -> t == null ? "+" : "-", (Object) null)); - assertEquals("+", mapNonNull(t -> t == null ? "+" : "-", (Object) null, null)); - assertEquals("+", mapNonNull(t -> t == null ? "+" : "-", (Object) null, null, null)); - } - - @Test - void testMapOnlyNonNull() { - assertEquals("f", mapOnlyNonNull(t -> t.substring(0, 1), "foo")); - assertEquals("f", mapOnlyNonNull(t -> t.substring(0, 1), "foo")); - assertEquals("f", mapOnlyNonNull(t -> t.substring(0, 1), "foo", null)); - assertEquals("f", mapOnlyNonNull(t -> t.substring(0, 1), null, "foo", null)); - assertEquals("f", mapOnlyNonNull(t -> t.substring(0, 1), null, "foo")); - assertEquals("f", mapOnlyNonNull(t -> t.substring(0, 1), "foo", "bar")); - assertEquals("f", mapOnlyNonNull(t -> t.substring(0, 1), "foo", null, "bar")); - assertEquals("f", mapOnlyNonNull(t -> t.substring(0, 1), null, "foo", null, "bar")); - assertEquals("f", mapOnlyNonNull(t -> t.substring(0, 1), null, "foo", "bar")); - assertNull(mapOnlyNonNull(t -> t.substring(0, 1), (String) null)); - assertNull(mapOnlyNonNull(t -> t.substring(0, 1), (String) null, null)); - assertNull(mapOnlyNonNull(t -> t.substring(0, 1), (String) null, null, null)); - } - - @Test - void testMapNonNullOrThrow() { - assertEquals("f", mapNonNullOrThrow(t -> t.substring(0, 1), "foo")); - assertEquals("f", mapNonNullOrThrow(t -> t.substring(0, 1), "foo", null)); - assertEquals("f", mapNonNullOrThrow(t -> t.substring(0, 1), null, "foo", null)); - assertEquals("f", mapNonNullOrThrow(t -> t.substring(0, 1), null, "foo")); - assertEquals("f", mapNonNullOrThrow(t -> t.substring(0, 1), "foo", "bar")); - assertEquals("f", mapNonNullOrThrow(t -> t.substring(0, 1), "foo", null, "bar")); - assertEquals("f", mapNonNullOrThrow(t -> t.substring(0, 1), null, "foo", null, "bar")); - assertEquals("f", mapNonNullOrThrow(t -> t.substring(0, 1), null, "foo", "bar")); - // mapping function should not be called so the one which allows nulls is used - assertThrows(NullPointerException.class, () -> mapNonNullOrThrow(Objects::isNull, (Object) null)); - assertThrows(NullPointerException.class, () -> mapNonNullOrThrow(Objects::isNull, (Object) null, null)); - assertThrows(NullPointerException.class, () -> mapNonNullOrThrow(Objects::isNull, (Object) null, null, null)); - } -} \ No newline at end of file diff --git a/deploy.sh b/deploy.sh deleted file mode 100644 index 9b247877b..000000000 --- a/deploy.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash - -echo 'Attempting to deploy artifacts if needed' - -# Verify branch set in Travis -echo "Branch: $TRAVIS_BRANCH" -if [[ -z ${TRAVIS_BRANCH} ]]; then # Exit if $TRAVIS_BRANCH is unset - echo 'Not Travis or branch undetected, exiting' - exit -1 -else - echo 'Valid branch' -fi - -# Verify pull-request status (should be false) -echo "Pull-request status: $TRAVIS_PULL_REQUEST" -if [[ ${TRAVIS_PULL_REQUEST} != 'false' ]]; then # Exit if TRAVIS_PULL_REQUEST is not 'false' - echo "Pull-request status should be 'false'" - exit -1 -else - echo 'Valid pull-request status' -fi - -# Verify that JAVA_HOME is set -if [[ -z ${JAVA_HOME} ]]; then # Exit if JAVA_HOME is unset - echo 'JAVA_HOME variable is unset, exiting' - exit -1; -fi - -# Get project version using special script -project_version=$(./project-version.sh) -echo "Got project version: ${project_version}" - -if [[ ${project_version} == *-SNAPSHOT ]]; then # Try to deploy snapshot if version ends with '-SNAPSHOT' - echo 'Snapshot version' - # Snapshots deployment happens only for `development` branch excluding pull requests to it (but including merges) - if [[ "$TRAVIS_BRANCH" = 'development' ]]; then - echo "Deploying ${project_version} to Sonatype repository" - ./.travis/deploy.sh - else - echo 'Not deploying as branch is not `development`' - fi -else # Try to deploy release if version doesn't end with '-SNAPSHOT' - echo 'Release version' - # Release deployment happens only for `releases` branch excluding pull requests to it (but including merges) - if [[ "$TRAVIS_BRANCH" = 'releases' ]]; then - echo "Deploying ${project_version} to Maven Central" - ./.travis/deploy.sh - else - echo 'Not deploying as branch is not `releases`' - fi -fi \ No newline at end of file diff --git a/config/pom.xml b/ez-config/pom.xml similarity index 91% rename from config/pom.xml rename to ez-config/pom.xml index 0a18e8d5f..f74561a03 100644 --- a/config/pom.xml +++ b/ez-config/pom.xml @@ -5,7 +5,7 @@ minecraft-utils ru.progrm-jarvis.minecraft - 0.1.0-SNAPSHOT + 1.0.0-SNAPSHOT ez-cfg @@ -19,7 +19,7 @@ minecraft-commons - ru.progrm-jarvis.reflector + ru.progrm-jarvis reflector @@ -32,4 +32,4 @@ jsr305 - \ No newline at end of file + diff --git a/fake-entity/pom.xml b/fake-entity-lib/pom.xml similarity index 64% rename from fake-entity/pom.xml rename to fake-entity-lib/pom.xml index 9d54b0f6a..70f09cf08 100644 --- a/fake-entity/pom.xml +++ b/fake-entity-lib/pom.xml @@ -5,34 +5,17 @@ ru.progrm-jarvis.minecraft minecraft-utils - 0.1.0-SNAPSHOT + 1.0.0-SNAPSHOT fake-entity-lib jar - - - - org.codehaus.mojo - aspectj-maven-plugin - - - - ru.progrm-jarvis.minecraft minecraft-commons - - ${project.parent.groupId} - nms-utils - - - ${project.parent.groupId} - player-utils - org.spigotmc @@ -40,19 +23,19 @@ com.comphenix.protocol - ProtocolLib-API + ProtocolLib - com.comphenix.packetwrapper - PacketWrapper + ru.progrm-jarvis.minecraft + packet-wrapper - ru.progrm-jarvis.reflector + ru.progrm-jarvis reflector - net.sf.trove4j - trove4j + it.unimi.dsi + fastutil @@ -60,8 +43,9 @@ lombok - org.aspectj - aspectjrt + org.jetbrains + annotations + 23.0.0 com.google.code.findbugs @@ -78,4 +62,4 @@ mockito-core - \ No newline at end of file + diff --git a/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractBasicFakeEntity.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractBasicFakeEntity.java new file mode 100644 index 000000000..a31ab23eb --- /dev/null +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractBasicFakeEntity.java @@ -0,0 +1,369 @@ +package ru.progrm_jarvis.minecraft.fakeentitylib.entity; + +import com.comphenix.protocol.wrappers.WrappedDataWatcher; +import com.comphenix.protocol.wrappers.WrappedWatchableObject; +import lombok.*; +import lombok.experimental.FieldDefaults; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +import static java.util.Collections.singletonList; + +/** + * Base for most common implementations of {@link BasicFakeEntity} containing player logic base. + */ +@ToString +@FieldDefaults(level = AccessLevel.PROTECTED) +public abstract class AbstractBasicFakeEntity extends AbstractPlayerContainingFakeEntity implements BasicFakeEntity { + + /** + * Metadata of this fake entity + */ + @Getter @Nullable WrappedDataWatcher metadata; + + /** + * Velocity of this fake entity + */ + @NonNull Vector velocity; + + /** + * Whether optimized packets should use for moving the entity or not + */ + boolean compactMoving; + + public AbstractBasicFakeEntity(final boolean global, final int viewDistance, + final @NonNull Location location, + final @NonNull Map players, + final @Nullable Vector velocity, final @Nullable WrappedDataWatcher metadata) { + super(viewDistance, global, location, players); + + this.velocity = velocity == null ? new Vector() : velocity; + this.metadata = metadata; + } + + /////////////////////////////////////////////////////////////////////////// + // Metadata + /////////////////////////////////////////////////////////////////////////// + + /** + * Sends metadata to all players seeing this entity creating packet if it has not yet been initialized. + */ + protected abstract void sendMetadata(); + + @Override + public void setMetadata(final @NonNull WrappedDataWatcher metadata) { + this.metadata = metadata.deepClone(); + + sendMetadata(); + } + + @Override + public void setMetadata(final @NonNull List metadata) { + this.metadata = new WrappedDataWatcher(metadata); + + sendMetadata(); + } + + @Override + public void setMetadata(final @NonNull Collection metadata) { + setMetadata(new ArrayList<>(metadata)); + + sendMetadata(); + } + + @Override + public void setMetadata(final @NonNull WrappedWatchableObject... metadata) { + setMetadata(Arrays.asList(metadata)); + + sendMetadata(); + } + + @Override + public void addMetadata(final Collection metadata) { + final WrappedDataWatcher thisMetadata; + if ((thisMetadata = this.metadata) == null) this.metadata = new WrappedDataWatcher(singletonList(metadata)); + else for (val metadatum : metadata) thisMetadata + .setObject(metadatum.getWatcherObject(), metadatum.getRawValue()); + + sendMetadata(); + } + + @Override + public void addMetadata(final WrappedWatchableObject... metadata) { + final WrappedDataWatcher thisMetadata; + if ((thisMetadata = this.metadata) == null) this.metadata = new WrappedDataWatcher(Arrays.asList(metadata)); + else for (val metadatum : metadata) thisMetadata + .setObject(metadatum.getWatcherObject(), metadatum.getRawValue()); + + sendMetadata(); + } + + @Override + public void removeMetadata(final Iterable indexes) { + final WrappedDataWatcher thisMetadata; + if ((thisMetadata = metadata) == null) return; + + for (val index : indexes) thisMetadata.remove(index); + + sendMetadata(); + } + + @Override + public void removeMetadata(final int... indexes) { + final WrappedDataWatcher thisMetadata; + if ((thisMetadata = metadata) == null) return; + + for (val index : indexes) thisMetadata.remove(index); + + sendMetadata(); + } + + /////////////////////////////////////////////////////////////////////////// + // Movement + /////////////////////////////////////////////////////////////////////////// + + /** + * Performs the movement of this living fake entity by given deltas and yaw and pitch specified + * not performing any checks such as 8-block limit of deltas or angle minimization. + * + * @param dx delta on X-axis + * @param dy delta on Y-axis + * @param dz delta on Z-axis + * @param yaw new yaw + * @param pitch new pitch + * @param sendVelocity {@code true} if velocity should be considered and {@code false} otherwise + */ + protected abstract void performMoveLook(double dx, double dy, double dz, + float yaw, float pitch, boolean sendVelocity); + /** + * Performs the movement of this living fake entity by given deltas and yaw and pitch specified + * not performing any checks such as 8-block limit of deltas. + * + * @param dx delta on X-axis + * @param dy delta on Y-axis + * @param dz delta on Z-axis + * @param sendVelocity {@code true} if velocity should be considered and {@code false} otherwise + */ + protected abstract void performMove(double dx, double dy, double dz, boolean sendVelocity); + + /** + * Performs the teleportation of this living fake entity to given coordinates changing yaw and pitch + * not performing any checks such as using movement for less than 8-block deltas or angle minimization. + * + * @param x new location on X-axis + * @param y new location on Y-axis + * @param z new location on Z-axis + * @param yaw new yaw + * @param pitch new pitch + * @param sendVelocity {@code true} if velocity should be considered and {@code false} otherwise + */ + protected abstract void performTeleportation(double x, double y, double z, + float yaw, float pitch, boolean sendVelocity); + + /** + * Performs the look by specified yaw and pitch. + * + * @param yaw new yaw + * @param pitch new pitch + */ + protected abstract void performLook(float yaw, float pitch); + + @Override + public void move(final double dx, final double dy, final double dz, final float dYaw, final float dPitch) { + if (compactMoving) performCompactMove(dx, dy, dz, dYaw, dPitch); + else performNonCompactMove(dx, dy, dz, dYaw, dPitch); + } + + protected void performCompactMove(final double dx, final double dy, final double dz, + final float dYaw, final float dPitch) { + val thisLocation = location; + if (dx == 0 && dy == 0 && dz == 0) { + if (dYaw != 0 || dPitch != 0) { + final float yaw = thisLocation.getYaw() + dYaw, pitch = thisLocation.getPitch() + dPitch; + + performLook(yaw, pitch); + + thisLocation.setYaw(yaw); + thisLocation.setPitch(pitch); + } + } else { + final Vector thisVelocity; + + (thisVelocity = velocity).setX(dx); + thisVelocity.setY(dy); + thisVelocity.setZ(dz); + // use teleportation if any of axises is above 8 blocks limit + if (dx > 8 || dy > 8 || dz > 8) { + final double x = thisLocation.getX() + dx, y = thisLocation.getY() + dy, z = thisLocation.getZ() + dz; + final float yaw = thisLocation.getYaw() + dYaw, pitch = thisLocation.getPitch() + dPitch; + + performTeleportation(x, y, z, pitch, yaw, true); + + thisLocation.setX(x); + thisLocation.setY(y); + thisLocation.setZ(z); + thisLocation.setYaw(yaw); + thisLocation.setPitch(pitch); + } + // otherwise use move + else { + if (dYaw == 0 && dPitch == 0) performMove(dx, dy, dz, true); + else { + performMoveLook(dx, dy, dz, dYaw, dPitch, true); + + thisLocation.setYaw(thisLocation.getYaw() + dYaw); + thisLocation.setPitch(thisLocation.getPitch() + dPitch); + } + + thisLocation.setX(thisLocation.getX() + dx); + thisLocation.setY(thisLocation.getY() + dy); + thisLocation.setZ(thisLocation.getZ() + dz); + } + + thisVelocity.setX(0); + thisVelocity.setY(0); + thisVelocity.setZ(0); + } + } + protected void performNonCompactMove(final double dx, final double dy, final double dz, + final float dYaw, final float dPitch) { + val thisLocation = location; + + var changeLocation = false; + double x = Double.NaN; + if (dx != 0) { + changeLocation = true; + + thisLocation.setX(x = (thisLocation.getX() + dx)); + } + double y = Double.NaN; + if (dy != 0) { + changeLocation = true; + + thisLocation.setY(y = (thisLocation.getY() + dy)); + } + double z = Double.NaN; + if (dz != 0) { + changeLocation = true; + + thisLocation.setZ(z = (thisLocation.getZ() + dz)); + } + + var changeLook = false; + float yaw = Float.NaN; + if (dYaw != 0) { + changeLook = true; + + thisLocation.setYaw(yaw = (thisLocation.getYaw() + dYaw)); + } + float pitch = Float.NaN; + if (dPitch != 0) { + changeLook = true; + + thisLocation.setPitch(pitch = (thisLocation.getPitch() + dPitch)); + } + + if (changeLocation) performTeleportation(x, y, z, yaw, pitch, false); + else if (changeLook) performLook(yaw, pitch); + } + + @Override + public void moveTo(final double x, final double y, final double z, final float yaw, final float pitch) { + if (compactMoving) performCompactTeleportation(x, y, z, yaw, pitch, true); + else performNonCompactTeleportation(x, y, z, yaw, pitch); + } + + @Override + public void teleport(final double x, final double y, final double z, final float yaw, final float pitch) { + if (compactMoving) performCompactTeleportation(x, y, z, yaw, pitch, false); + else performNonCompactTeleportation(x, y, z, yaw, pitch); + } + + protected void performCompactTeleportation(final double x, final double y, final double z, + final float yaw, final float pitch, final boolean sendVelocity) { + final Location thisLocation; + final double + dx = x - (thisLocation = location).getX(), + dy = y - thisLocation.getY(), + dz = z - thisLocation.getZ(); + + if (dx == 0 && dy == 0 && dz == 0) { + if (yaw != thisLocation.getYaw() || pitch != thisLocation.getPitch()) { + performLook(yaw, pitch); + + thisLocation.setYaw(yaw); + thisLocation.setPitch(pitch); + } + } else { + final Vector thisVelocity; + (thisVelocity = velocity).setX(dx); + thisVelocity.setY(dy); + thisVelocity.setZ(dz); + + if (dx > 8 || dy > 8 || dz > 8) performTeleportation(x, y, z, yaw, pitch, sendVelocity); + else if (yaw != thisLocation.getYaw() || pitch != thisLocation.getPitch()) { + performMoveLook(dx, dy, dz, yaw, pitch, sendVelocity); + + thisLocation.setYaw(yaw); + thisLocation.setPitch(pitch); + } else performMove(dx, dy, dz, sendVelocity); + + thisLocation.setX(x); + thisLocation.setY(y); + thisLocation.setZ(z); + + thisVelocity.setX(0); + thisVelocity.setY(0); + thisVelocity.setZ(0); + } + } + + protected void performNonCompactTeleportation(final double x, final double y, final double z, + final float yaw, final float pitch) { + var changeLocation = false; + + final Location thisLocation; + if (x != (thisLocation = location).getX()) { + changeLocation = true; + + thisLocation.setX(x); + } + if (y != thisLocation.getY()) { + changeLocation = true; + + thisLocation.setY(y); + } + if (z != thisLocation.getZ()) { + changeLocation = true; + + thisLocation.setZ(z); + } + var changeLook = false; + if (yaw != thisLocation.getYaw()) { + changeLook = true; + + thisLocation.setYaw(yaw); + } + if (pitch != thisLocation.getPitch()) { + changeLook = true; + + thisLocation.setPitch(pitch); + } + + if (changeLocation) performTeleportation(x, y, z, yaw, pitch, false); + else if (changeLook) performLook(yaw, pitch); + } + + @Override + public void syncLocation() { + final Location thisLocation; + performTeleportation( + (thisLocation = location).getX(), thisLocation.getY(), thisLocation.getZ(), + thisLocation.getYaw(), thisLocation.getPitch(), false + ); + } +} diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractFakeEntity.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractFakeEntity.java similarity index 66% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractFakeEntity.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractFakeEntity.java index 1ddc8de95..aae3189ef 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractFakeEntity.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractFakeEntity.java @@ -4,19 +4,25 @@ import lombok.experimental.FieldDefaults; import org.bukkit.Location; import org.bukkit.World; +import ru.progrm_jarvis.javacommons.annotation.DontOverrideEqualsAndHashCode; /** * Base for most common implementations of {@link FakeEntity}. */ +@DontOverrideEqualsAndHashCode("Entities are mutable and have no real IDs in practise") @ToString @RequiredArgsConstructor -@EqualsAndHashCode(callSuper = false) @FieldDefaults(level = AccessLevel.PROTECTED) public abstract class AbstractFakeEntity implements FakeEntity { - @NonNull @Getter Location location; + final @NonNull Location location; @Getter boolean visible = true; // setter should be created manually to perform visualisation logic + @Override + public Location getLocation() { + return location.clone(); + } + @Override public World getWorld() { return location.getWorld(); diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractObservableFakeEntity.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractObservableFakeEntity.java similarity index 52% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractObservableFakeEntity.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractObservableFakeEntity.java index 42a4635f8..49296c6e0 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractObservableFakeEntity.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractObservableFakeEntity.java @@ -9,27 +9,26 @@ * Base for most common implementations of {@link ObservableFakeEntity}. */ @ToString -@EqualsAndHashCode(callSuper = false) -@FieldDefaults(level = AccessLevel.PROTECTED) +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) public abstract class AbstractObservableFakeEntity extends AbstractFakeEntity implements ObservableFakeEntity { - - @Getter final boolean global; - @Getter final int viewDistance; - @Getter final int viewDistanceSquared; + @Getter boolean global; + @Getter int viewDistance; + @Getter int viewDistanceSquared; public AbstractObservableFakeEntity(final boolean global, final int viewDistance, - @NonNull final Location location) { + final @NonNull Location location) { super(location); this.global = global; this.viewDistance = viewDistance; - this.viewDistanceSquared = viewDistance * viewDistance; + viewDistanceSquared = viewDistance * viewDistance; } @Override - public boolean canSee(final Player player) { - return player.getWorld() == location.getWorld() - && player.getEyeLocation().distanceSquared(location) <= viewDistanceSquared; + public boolean shouldSee(final Player player) { + final Location thisLocation; + return player.getWorld() == (thisLocation = location).getWorld() + && player.getEyeLocation().distanceSquared(thisLocation) <= viewDistanceSquared; } } diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractPlayerContainingFakeEntity.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractPlayerContainingFakeEntity.java similarity index 53% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractPlayerContainingFakeEntity.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractPlayerContainingFakeEntity.java index b17a6a383..8ec9688ef 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractPlayerContainingFakeEntity.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractPlayerContainingFakeEntity.java @@ -4,36 +4,39 @@ import lombok.experimental.FieldDefaults; import org.bukkit.Location; import org.bukkit.entity.Player; +import ru.progrm_jarvis.minecraft.commons.player.registry.PlayerRegistryRegistration; -import java.util.Collection; -import java.util.Map; +import java.util.*; /** * Base for most common implementations of {@link ObservableFakeEntity} containing player logic base. */ @ToString -@EqualsAndHashCode(callSuper = false) -@FieldDefaults(level = AccessLevel.PROTECTED) +@FieldDefaults(makeFinal = true, level = AccessLevel.PROTECTED) +@PlayerRegistryRegistration(PlayerRegistryRegistration.Policy.MANUAL) public abstract class AbstractPlayerContainingFakeEntity extends AbstractObservableFakeEntity { - @NonNull final Map players; + @NonNull Map players; + @NonNull Set playersView; public AbstractPlayerContainingFakeEntity(final int viewDistance, final boolean global, - @NonNull final Location location, - @NonNull final Map players) { + final @NonNull Location location, + final @NonNull Map players) { super(global, viewDistance, location); if (!players.isEmpty()) players.clear(); + this.players = players; + playersView = Collections.unmodifiableSet(players.keySet()); } @Override - public Collection getPlayers() { - return players.keySet(); + public Collection getPlayers() { + return playersView; } @Override - public boolean isRendered(@NonNull final Player player) { + public boolean isRendered(final @NonNull Player player) { return players.getOrDefault(player, false); } @@ -44,21 +47,37 @@ public boolean containsPlayer(final Player player) { @Override public void addPlayer(final Player player) { - if (!players.containsKey(player)) { - if (canSee(player)) render(player); - else players.put(player, false); - } + final Map thisPlayers; + if (!(thisPlayers = players).containsKey(player)) if (shouldSee(player)) render(player); + else thisPlayers.put(player, false); } @Override public void removePlayer(final Player player) { - val canSee = players.get(player); - if (canSee != null) { + final Map thisPlayers; + final Boolean canSee; + if ((canSee = (thisPlayers = players).get(player)) != null) { if (canSee) unrender(player); - else players.remove(player); + thisPlayers.remove(player); } } + @Override + public Collection getSeeingPlayers() { + val seeingPlayers = new HashSet(); + for (val entry : players.entrySet()) if (entry.getValue()) seeingPlayers.add(entry.getKey()); + + return seeingPlayers; + } + + @Override + public Collection getNotSeeingPlayers() { + val notSeeingPlayers = new HashSet(); + for (val entry : players.entrySet()) if (!entry.getValue()) notSeeingPlayers.add(entry.getKey()); + + return notSeeingPlayers; + } + /////////////////////////////////////////////////////////////////////////// // Rendering /////////////////////////////////////////////////////////////////////////// @@ -83,8 +102,8 @@ public void attemptRerender(final Player player) { if (sees == null) return; if (sees) { - if (!canSee(player)) render(player); - } else if (canSee(player)) unrender(player); + if (!shouldSee(player)) unrender(player); + } else if (shouldSee(player)) render(player); } @@ -94,8 +113,8 @@ public void attemptRerenderForAll() { val player = entry.getKey(); if (entry.getValue()) { // sees - if (!canSee(player)) unrender(player); - } else if (canSee(player)) render(player); + if (!shouldSee(player)) unrender(player); + } else if (shouldSee(player)) render(player); } } } diff --git a/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/ArmorStandBlockItem.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/ArmorStandBlockItem.java new file mode 100644 index 000000000..099fac61d --- /dev/null +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/ArmorStandBlockItem.java @@ -0,0 +1,285 @@ +package ru.progrm_jarvis.minecraft.fakeentitylib.entity; + +import com.comphenix.packetwrapper.WrapperPlayServerEntityEquipment; +import com.comphenix.protocol.wrappers.EnumWrappers; +import com.comphenix.protocol.wrappers.Vector3F; +import com.comphenix.protocol.wrappers.WrappedDataWatcher; +import com.comphenix.protocol.wrappers.WrappedWatchableObject; +import lombok.*; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.progrm_jarvis.javacommons.ownership.annotation.Own; +import ru.progrm_jarvis.minecraft.commons.nms.NmsUtil; +import ru.progrm_jarvis.minecraft.commons.nms.metadata.MetadataGenerator.ArmorStand.ArmorStandFlag; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import static java.lang.Math.*; +import static ru.progrm_jarvis.minecraft.commons.nms.metadata.MetadataGenerator.ArmorStand.ArmorStandFlag.*; +import static ru.progrm_jarvis.minecraft.commons.nms.metadata.MetadataGenerator.ArmorStand.armorStandFlags; +import static ru.progrm_jarvis.minecraft.commons.nms.metadata.MetadataGenerator.ArmorStand.headRotation; +import static ru.progrm_jarvis.minecraft.commons.nms.metadata.MetadataGenerator.Entity.*; + +/** + * A fake small (or very small) movable block-item which can be normally rotated over all axises + * and have floating point coordinates. This block is displayed as an item on head of invisible armor stand. + */ +@FieldDefaults(level = AccessLevel.PROTECTED) +public class ArmorStandBlockItem extends SimpleLivingFakeEntity { + + protected static final double PIXEL_SIZE = 0x1p-4, + HALF_PIXEL_SIZE = 0x1p-5, + ARMOR_STAND_BODY_HEIGHT = (16 + 8) * PIXEL_SIZE, + ARMOR_STAND_HEAD_ROOT_OFFSET = ARMOR_STAND_BODY_HEIGHT - PIXEL_SIZE - HALF_PIXEL_SIZE, + ITEM_CENTER_Y_OFFSET = 3 * PIXEL_SIZE + HALF_PIXEL_SIZE; + // offset of the item center from the rotation center + + final boolean small, marker; + final double itemCenterYOffset; + + @NotNull Offset offset; + //double xOffset, yOffset, zOffset; + + /** + * Rotation of this block + */ + @Nullable Vector3F rotation; + + /** + * Item displayed by this block + */ + @NonNull ItemStack item; + + /** + * Packet used for displaying this block's displayed item + */ + WrapperPlayServerEntityEquipment equipmentPacket; + + /** + * Initializes a newly created armor stand block-item from parameters given. + * + * @param uuid unique entity ID of this block-item entity + * @param playersMap map to be used as backend for this block-item entity + * @param global whether this block-item is global (the value returned by {@link #isGlobal()}) + * @param visible whether this block-item is initially be visible + * @param viewDistance view distance of this block-item + * @param location location of this block-item + * @param rotation rotation of this block item + * @param itemCenterYOffset offset of the item center on Y-axis + * @param offset offset of the entity from its logical center + * @param small whether this block-item is small + * @param marker whether this block-item is marker + * @param item item to be displayed by this block-item + */ + protected ArmorStandBlockItem(final @Nullable UUID uuid, + final @NotNull Map<@NotNull Player, @NotNull Boolean> playersMap, + final boolean global, final int viewDistance, final boolean visible, + final @NotNull Location location, final @NotNull Vector3F rotation, + final double itemCenterYOffset, final @NotNull Offset offset, + final boolean small, final boolean marker, final @NotNull ItemStack item) { + super( + NmsUtil.nextEntityId(), uuid, EntityType.ARMOR_STAND, + playersMap, global, viewDistance, visible, location, 0, null, createMetadata(rotation, small, marker) + ); + + this.small = small; + this.marker = marker; + this.itemCenterYOffset = itemCenterYOffset; + + this.rotation = rotation; + this.offset = offset; + + final WrapperPlayServerEntityEquipment thisEquipmentPacket; + equipmentPacket = thisEquipmentPacket = new WrapperPlayServerEntityEquipment(); + thisEquipmentPacket.setEntityID(entityId); + thisEquipmentPacket.setSlot(EnumWrappers.ItemSlot.HEAD); + thisEquipmentPacket.setItem(this.item = item); + } + + /** + * Creates new armor stand block-item by parameters specified. + * + * @param uuid unique ID of the created entity + * @param concurrent whether created block-item supports concurrent modification of players related to it + * @param global whether created block-item is global (the value returned by {@link #isGlobal()}) + * @param viewDistance view distance of created block-item + * @param visible whether created block-item should be visible + * @param location location of created block-item + * @param rotation rotation of created block item + * @param small whether created block-item is small + * @param marker whether created block-item is marker + * @param item item to be displayed by this block-item + * @return newly created armor stand block-item + */ + public static ArmorStandBlockItem create(final @Nullable UUID uuid, + final boolean concurrent, + final boolean global, final int viewDistance, final boolean visible, + final @Own @NonNull Location location, + final @Own @NonNull Vector3F rotation, + final boolean small, final boolean marker, final @NonNull ItemStack item) { + final double itemCenterYOffset; + final Offset offset; + (offset = rotationOffsets( + rotation, itemCenterYOffset = small ? ITEM_CENTER_Y_OFFSET * 0x1p-1 : ITEM_CENTER_Y_OFFSET) + ).applyTo(location); + + return new ArmorStandBlockItem( + uuid, concurrent ? new ConcurrentHashMap<>() : new HashMap<>(), + global, viewDistance, visible, + location.add(0, -(small ? ARMOR_STAND_HEAD_ROOT_OFFSET / 2 : ARMOR_STAND_HEAD_ROOT_OFFSET), 0), + rotation, itemCenterYOffset, offset, small, marker, item + ); + } + + @Override + public @NonNull Location getLocation() { + final Location location; + offset.applyTo(location = super.getLocation()); + + return location; + } + + protected static @NotNull Offset rotationOffsets(final Vector3F rotation, double yOffset /* => y */) { + // apply rotation matrices to align center: https://en.wikipedia.org/wiki/Rotation_matrix + // let L be initial location and Q be geometrical center + // the resulting location should be L' = L - Q' + // where Q' = Mx(xRotation) * My(yRotation) * Mz(zRotation) * Q + // and Mx, My and Mz are rotation matrices for the axes X, Y and Z respectively + + // for non-optimized implementation see commit 58899ac9450afb1e11e4a3b1ab923c139f4c7a29 + + double angle; + val z = -yOffset * sin(angle = toRadians(rotation.getX())); + // minuses are used as we need to go to center instead of going from it + return SimpleOffset.create( + (yOffset *= cos(angle)) * sin(angle = toRadians(rotation.getZ())), -yOffset * cos(angle), z + ); + } + + /** + * Creates valid metadata for armor stand block-item. + * + * @param rotation rotation of this block-item + * @param small whether this block-item is small + * @param marker whether this block-item is marker + * @return created metadata object + */ + protected static WrappedDataWatcher createMetadata(final @Nullable Vector3F rotation, + final boolean small, final boolean marker) { + val metadata = new ArrayList(); + + metadata.add(air(300)); + metadata.add(noGravity(true)); + if (marker) { + metadata.add(entityFlags(EntityFlag.INVISIBLE, EntityFlag.ON_FIRE)); + metadata.add(armorStandFlags(small ? new ArmorStandFlag[]{ + SMALL, NO_BASE_PLATE, MARKER + } : new ArmorStandFlag[]{ + MARKER, NO_BASE_PLATE + })); + } else { + metadata.add(entityFlags(EntityFlag.INVISIBLE)); + metadata.add(armorStandFlags(small + ? new ArmorStandFlag[]{SMALL, NO_BASE_PLATE} + : new ArmorStandFlag[]{NO_BASE_PLATE} + )); + } + if (rotation != null) metadata.add(headRotation(rotation)); + + return new WrappedDataWatcher(metadata); + } + + @Override + protected void performSpawnNoChecks(final @NotNull Player player) { + super.performSpawnNoChecks(player); + equipmentPacket.sendPacket(player); + } + + /** + * Sets this blocks rotation to the one specified. + * + * @param rotation new rotation of this block + */ + protected void setRotationNoChecks(final @Own @NotNull Vector3F rotation) { + { // overwrite the head's offset + final Offset oldOffset = offset, newOffset; + move( + (newOffset = offset = rotationOffsets(rotation, itemCenterYOffset)).x() - oldOffset.x(), + newOffset.y() - oldOffset.y(), + newOffset.z() - oldOffset.z() + ); + } + // overwrite the head's rotation + addMetadata(headRotation(rotation)); + } + + /** + * Rotates this block by specified delta. This means that its current + * roll (x), pitch (y) and yaw (z) will each get incremented by those of delta specified. + * + * @param delta delta of rotation + */ + public void rotate(final @NonNull @Own Vector3F delta) { + final float dx, dy = delta.getY(), dz = delta.getZ(); + if (((dx = delta.getX()) == 0) && dy == 0 && dz == 0) return; // no-op + + final Vector3F thisRotation; + if (((thisRotation = rotation) != null)) { + delta.setX(thisRotation.getX() + dx); + delta.setY(thisRotation.getY() + dy); + delta.setZ(thisRotation.getZ() + dz); + } + setRotationNoChecks(delta); + } + + /** + * Sets this blocks rotation to the one specified. + * + * @param newRotation new rotation of this block + */ + public void setRotation(final @Own @NonNull Vector3F newRotation) { + if (!newRotation.equals(rotation)) setRotationNoChecks(newRotation); + } + + public void setItem(final @Own @NonNull ItemStack item) { + equipmentPacket.setItem(this.item = item); + for (val entry : players.entrySet()) if (entry.getValue()) equipmentPacket.sendPacket(entry.getKey()); + } + + protected interface Offset { + double x(); + + double y(); + + double z(); + + void applyTo(@NotNull Location location); + } + + @Value + @Accessors(fluent = true) + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + protected static class SimpleOffset implements Offset { + double x, y, z; + + @Override + public void applyTo(final @NotNull Location location) { + location.add(x, y, z); + } + + public static @NotNull + Offset create(final double x, final double y, final double z) { + return new SimpleOffset(x, y, z); + } + } +} diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/BasicFakeEntity.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/BasicFakeEntity.java similarity index 71% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/BasicFakeEntity.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/BasicFakeEntity.java index 3e387055f..ab45b428e 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/BasicFakeEntity.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/BasicFakeEntity.java @@ -5,8 +5,8 @@ import lombok.NonNull; import org.bukkit.Location; import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; -import javax.annotation.Nonnull; import java.util.Collection; import java.util.List; @@ -27,33 +27,48 @@ public interface BasicFakeEntity extends ObservableFakeEntity { void despawn(); /////////////////////////////////////////////////////////////////////////// - // Location + // Dimensional /////////////////////////////////////////////////////////////////////////// - void teleport(double x, double y, double z, float yaw, float pitch); - - default void teleport(final double x, final double y, final double z) { - teleport(x, y, z, 0, 0); - } - - default void teleport(@NonNull final Location location) { - teleport(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); - } - void move(double dx, double dy, double dz, float dYaw, float dPitch); default void move(final double dx, final double dy, final double dz) { move(dx, dy, dz, 0, 0); } - default void move(@NonNull final Vector direction, float dYaw, float dPitch) { + default void move(final @NonNull Vector direction, float dYaw, float dPitch) { move(direction.getX(), direction.getY(), direction.getZ(), dYaw, dPitch); } - default void move(@NonNull final Vector direction) { + default void move(final @NonNull Vector direction) { move(direction, 0, 0); } + void moveTo(double x, double y, double z, float yaw, float pitch); + + default void moveTo(final double x, final double y, final double z) { + moveTo(x, y, z, 0, 0); + } + + default void moveTo(final @NonNull Location location) { + moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + } + + void teleport(double x, double y, double z, float yaw, float pitch); + + default void teleport(final double x, final double y, final double z) { + teleport(x, y, z, 0, 0); + } + + default void teleport(final @NonNull Location location) { + teleport(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + } + + /** + * Performs the location synchronization so that the clients surely have the exact location of the entity. + */ + void syncLocation(); + /////////////////////////////////////////////////////////////////////////// // Metadata /////////////////////////////////////////////////////////////////////////// @@ -64,11 +79,9 @@ default void move(@NonNull final Vector direction) { void setMetadata(List metadata); - void setMetadata(@Nonnull final Collection metadata); - - void setMetadata(@Nonnull final WrappedWatchableObject... metadata); + void setMetadata(final @NotNull Collection metadata); - void addMetadata(List metadata); + void setMetadata(final @NotNull WrappedWatchableObject... metadata); void addMetadata(Collection metadata); diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/FakeEntity.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/FakeEntity.java similarity index 64% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/FakeEntity.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/FakeEntity.java index 84aa0fbdb..64451e14f 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/FakeEntity.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/FakeEntity.java @@ -3,12 +3,19 @@ import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; -import ru.progrm_jarvis.minecraft.playerutils.collection.PlayerContainer; +import ru.progrm_jarvis.minecraft.commons.player.collection.PlayerContainer; import java.util.Collection; public interface FakeEntity extends PlayerContainer { + /** + * Gets the unique ID of this entity for use in player packets. + * + * @return Minecraft entity unique ID of this fake entity + */ + int getEntityId(); + /** * Gets the world of this fake entity. * @@ -17,10 +24,11 @@ public interface FakeEntity extends PlayerContainer { World getWorld(); /** - * Gets location of this fake entity, the object returned should not be modified without cloning - * as it may be an actual fake entity's location object. + * Gets location of this fake entity. * * @return location of this fake entity + * + * @apiNote changes to the returned location will not affect the fake entity */ Location getLocation(); @@ -29,7 +37,7 @@ public interface FakeEntity extends PlayerContainer { * * @return all players associated with this entity */ - Collection getPlayers(); + Collection getPlayers(); /** * Gets whether this fake entity is visible or not. @@ -44,4 +52,9 @@ public interface FakeEntity extends PlayerContainer { * @param visible {@code true} if this fake entity should be visible or {@code false} if it should be invisible */ void setVisible(boolean visible); + + /** + * Removes the fake entity which guarantees that no other cleanups will be required. + */ + void remove(); } diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/ObservableFakeEntity.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/ObservableFakeEntity.java similarity index 78% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/ObservableFakeEntity.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/ObservableFakeEntity.java index d569c125f..c5cdee392 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/ObservableFakeEntity.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/ObservableFakeEntity.java @@ -3,15 +3,9 @@ import lombok.val; import org.bukkit.entity.Player; -public interface ObservableFakeEntity extends FakeEntity { +import java.util.Collection; - /** - * Gets whether or not this fake entity should be visible for all players online - * which means that observer will attempt to add players to it whenever they join game and remove them on leave. - * - * @return whether or not this fake entity is global - */ - boolean isGlobal(); +public interface ObservableFakeEntity extends FakeEntity { /** * Gets view distance for this fake entity. This may be not present in which case this returns {@code -1}. @@ -38,7 +32,23 @@ public interface ObservableFakeEntity extends FakeEntity { * @param player player to check for ability to see this fake entity * @return whether or not the player can see this fake entity */ - boolean canSee(Player player); + boolean shouldSee(Player player); + + /** + * Gets all players who are related to this fake entity + * and are seeing it at the moment (have it rendered). + * + * @return all players who have this fake entity rendered at the moment + */ + Collection getSeeingPlayers(); + + /** + * Gets all players who are related to this fake entity + * and are not seeing it at the moment (don't have it rendered). + * + * @return all players who don't have this fake entity rendered at the moment + */ + Collection getNotSeeingPlayers(); /** * Attempt to rerender this fake entity for player specified. diff --git a/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/SimpleLivingFakeEntity.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/SimpleLivingFakeEntity.java new file mode 100644 index 000000000..98d29e594 --- /dev/null +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/SimpleLivingFakeEntity.java @@ -0,0 +1,458 @@ +package ru.progrm_jarvis.minecraft.fakeentitylib.entity; + +import com.comphenix.packetwrapper.*; +import com.comphenix.protocol.wrappers.WrappedDataWatcher; +import lombok.*; +import lombok.experimental.FieldDefaults; +import lombok.experimental.NonFinal; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.progrm_jarvis.minecraft.commons.nms.NmsUtil; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * A simple living entity self-sustained for direct usage. + */ +@ToString +@FieldDefaults(level = AccessLevel.PROTECTED) +public class SimpleLivingFakeEntity extends AbstractBasicFakeEntity { + + /** + * Minor version of Minecraft. + */ + private static final int MINECRAFT_MINOR_VERSION = NmsUtil.getVersion().getGeneration(); + + /////////////////////////////////////////////////////////////////////////// + // Basic entity data + /////////////////////////////////////////////////////////////////////////// + + /** + * Unique entity ID by which it should be identified in all packets. + */ + @Getter final int entityId; // id should not be generated before all checks are performed + + /** + * This fake entity's UUID + */ + final @Nullable UUID uuid; // may be null as it is not required + + /** + * Type of this entity + */ + final EntityType type; // type of entity + + /////////////////////////////////////////////////////////////////////////// + // General for FakeEntity + /////////////////////////////////////////////////////////////////////////// + + /** + * PLayers related to this fake entity + */ + final @NonNull Map players; + + /** + * Whether or not this fake entity is global + */ + @Getter final boolean global; + + /** + * View distance for this entity or {@code -1} if none + */ + @Getter final int viewDistance; + + /////////////////////////////////////////////////////////////////////////// + // Entity changing parameters + /////////////////////////////////////////////////////////////////////////// + + /** + * Head pitch of this fake entity + */ + float headPitch; + + /** + * Metadata of this fake entity + */ + @Getter @Nullable WrappedDataWatcher metadata; + + // packets should not be created before id is generated + + /** + * Packet used for spawning this fake entity + */ + WrapperPlayServerSpawnEntityLiving spawnPacket; + + /** + * Packet used for despawning this fake entity + */ + WrapperPlayServerEntityDestroy despawnPacket; + + // packets which should be initialized only when first needed + + /** + * Packet used for updating this fake entity's metadata + */ + @NonFinal WrapperPlayServerEntityMetadata metadataPacket; + + /** + * Packet used for moving this fake entity (not more than 8 blocks per axis) without modifying head rotation + */ + WrapperPlayServerRelEntityMove movePacket; + + /** + * Packet used for modifying entity's head rotation + */ + WrapperPlayServerEntityLook lookPacket; + + /** + * Packet used for moving this fake entity (not more than 8 blocks per axis) and modifying head rotation + */ + WrapperPlayServerRelEntityMoveLook moveLookPacket; + + /** + * Packet used for teleporting this fake entity + */ + WrapperPlayServerEntityTeleport teleportPacket; + + /** + * Packet used for fake entity velocity + */ + WrapperPlayServerEntityVelocity velocityPacket; + + @Builder + public SimpleLivingFakeEntity(final int entityId, final @Nullable UUID uuid, + // Start of entities properties, TODO specific class + final @NonNull EntityType type, + // End of entity's properties + final @NonNull Map players, + final boolean global, final int viewDistance, + boolean visible, final @NonNull Location location, float headPitch, + final @Nullable Vector velocity, final @Nullable WrappedDataWatcher metadata) { + super(global, viewDistance, location, players, velocity, metadata); + + // setup fields + + this.entityId = entityId; + this.uuid = uuid; + this.type = type; + + this.players = players; + this.global = global; + this.viewDistance = Math.max(-1, viewDistance); + + this.visible = visible; + + this.headPitch = headPitch; + + this.metadata = metadata; + + // setup packets + + { + final WrapperPlayServerSpawnEntityLiving thisSpawnPacket; + spawnPacket = thisSpawnPacket = new WrapperPlayServerSpawnEntityLiving(); + thisSpawnPacket.setEntityID(entityId); + thisSpawnPacket.setType(type); + if (uuid != null) thisSpawnPacket.setUniqueId(uuid); + } + + { + final WrapperPlayServerEntityDestroy thisDespawnPacket; + despawnPacket = thisDespawnPacket = new WrapperPlayServerEntityDestroy(); + thisDespawnPacket.setEntityIds(new int[]{entityId}); + } + } + + /** + * Spawns the entity for player without performing any checks + * such as player containment checks or spawn packet actualization. + * + * @param player player to whom to spawn this entity + */ + protected void performSpawnNoChecks(final Player player) { + spawnPacket.sendPacket(player); + metadataPacket.sendPacket(player); + } + + /** + * Despawns the entity for player without performing any checks + * such as player containment checks or spawn packet actualization. + * + * @param player player to whom to despawn this entity + */ + protected void performDespawnNoChecks(final Player player) { + despawnPacket.sendPacket(player); + } + + protected void actualizeSpawnPacket() { + final WrapperPlayServerSpawnEntityLiving thisSpawnPacket; + { + final Location thisLocation; + (thisSpawnPacket = spawnPacket).setX((thisLocation = location).getX()); + thisSpawnPacket.setY(thisLocation.getY()); + thisSpawnPacket.setZ(thisLocation.getZ()); + + thisSpawnPacket.setPitch(thisLocation.getPitch()); + thisSpawnPacket.setYaw(thisLocation.getYaw()); + thisSpawnPacket.setHeadPitch(headPitch); + } + + { + final Vector thisVelocity; + thisSpawnPacket.setVelocityX((thisVelocity = velocity).getX()); + thisSpawnPacket.setVelocityY(thisVelocity.getY()); + thisSpawnPacket.setVelocityZ(thisVelocity.getZ()); + } + } + + protected void actualizeMetadataPacket(final @NotNull WrappedDataWatcher metadata) { + WrapperPlayServerEntityMetadata thisMetadataPacket; + if ((thisMetadataPacket = metadataPacket) == null) { + metadataPacket = thisMetadataPacket = new WrapperPlayServerEntityMetadata(); + thisMetadataPacket.setEntityID(entityId); + } + thisMetadataPacket.setMetadata(metadata.getWatchableObjects()); + } + + protected void actualizeMetadataPacket() { + final WrappedDataWatcher thisMetadata; + if ((thisMetadata = metadata) != null) actualizeMetadataPacket(thisMetadata); + } + + /////////////////////////////////////////////////////////////////////////// + // Spawning / Despawning + /////////////////////////////////////////////////////////////////////////// + + @Override + public void spawn() { + if (visible) { + actualizeSpawnPacket(); + actualizeMetadataPacket(); + + for (val entry : players.entrySet()) if (entry.getValue()) performSpawnNoChecks(entry.getKey()); + } + } + + @Override + public void despawn() { + if (visible) for (val entry : players.entrySet()) if (entry.getValue()) performDespawnNoChecks(entry.getKey()); + } + + /////////////////////////////////////////////////////////////////////////// + // Movement + /////////////////////////////////////////////////////////////////////////// + + protected boolean isOnGround() { + final Location thisLocation; + //noinspection ConstantConditions #getWorld() may but shouldn't return null + return (thisLocation = location).getY() % 1 == 0 && thisLocation.getWorld() + .getBlockAt(thisLocation).getType().isSolid(); + } + + protected boolean hasVelocity() { + return velocity.length() != 0; + } + + /** + * Updates the velocity packet initializing it if it haven;t been initialized. + * + * @apiNote call to this method guarantees that {@link #velocityPacket} won't be {@code null} after it + */ + protected void actualizeVelocityPacket() { + WrapperPlayServerEntityVelocity packet; + if ((packet = velocityPacket) == null) { + packet = velocityPacket = new WrapperPlayServerEntityVelocity(); + packet.setEntityID(entityId); + } + + final Vector thisVelocity; + packet.setVelocityX((thisVelocity = velocity).getX()); + packet.setVelocityY(thisVelocity.getY()); + packet.setVelocityZ(thisVelocity.getZ()); + } + + @Override + @SuppressWarnings("Duplicates") + protected void performMoveLook(final double dx, final double dy, final double dz, + final float yaw, final float pitch, boolean sendVelocity) { + if (visible) { + WrapperPlayServerRelEntityMoveLook thisMoveLookPacket; + if ((thisMoveLookPacket = moveLookPacket) == null) { + moveLookPacket = thisMoveLookPacket = new WrapperPlayServerRelEntityMoveLook(); + thisMoveLookPacket.setEntityID(entityId); + } + + thisMoveLookPacket.setDx(dx); + thisMoveLookPacket.setDy(dy); + thisMoveLookPacket.setDz(dz); + thisMoveLookPacket.setYaw(yaw); + thisMoveLookPacket.setPitch(pitch); + thisMoveLookPacket.setOnGround(isOnGround()); + + sendVelocity = sendVelocity && hasVelocity(); + if (sendVelocity) actualizeVelocityPacket(); + + final Set> entries; + if ((!(entries = players.entrySet()).isEmpty())) { + val thisVelocityPacket = sendVelocity ? velocityPacket : null; + + for (val entry : entries) if (entry.getValue()) { + val player = entry.getKey(); + + if (sendVelocity) thisVelocityPacket.sendPacket(player); + thisMoveLookPacket.sendPacket(player); + } + } + } + } + + @Override + @SuppressWarnings("Duplicates") + protected void performMove(final double dx, final double dy, final double dz, boolean sendVelocity) { + if (visible) { + WrapperPlayServerRelEntityMove thisMovePacket; + if ((thisMovePacket = movePacket) == null) { + movePacket = thisMovePacket = new WrapperPlayServerRelEntityMove(); + thisMovePacket.setEntityID(entityId); + } + + thisMovePacket.setDx(dx); + thisMovePacket.setDy(dy); + thisMovePacket.setDz(dz); + thisMovePacket.setOnGround(isOnGround()); + + sendVelocity = sendVelocity && hasVelocity(); + if (sendVelocity) actualizeVelocityPacket(); + + final Set> entries; + if ((!(entries = players.entrySet()).isEmpty())) { + val thisVelocityPacket = sendVelocity ? velocityPacket : null; + + for (val entry : entries) if (entry.getValue()) { + val player = entry.getKey(); + + if (sendVelocity) thisVelocityPacket.sendPacket(player); + thisMovePacket.sendPacket(player); + } + } + } + } + + @Override + @SuppressWarnings("Duplicates") + protected void performTeleportation(final double x, final double y, final double z, + final float yaw, final float pitch, boolean sendVelocity) { + if (visible) { + WrapperPlayServerEntityTeleport thisTeleportPacket; + if ((thisTeleportPacket = teleportPacket) == null) { + teleportPacket = thisTeleportPacket = new WrapperPlayServerEntityTeleport(); + thisTeleportPacket.setEntityID(entityId); + } + + thisTeleportPacket.setX(x); + thisTeleportPacket.setY(y); + thisTeleportPacket.setZ(z); + thisTeleportPacket.setYaw(yaw); + thisTeleportPacket.setPitch(pitch); + thisTeleportPacket.setOnGround(isOnGround()); + + sendVelocity = sendVelocity && hasVelocity(); + if (sendVelocity) actualizeVelocityPacket(); + + final Set> entries; + if ((!(entries = players.entrySet()).isEmpty())) { + val thisVelocityPacket = sendVelocity ? velocityPacket : null; + + for (val entry : entries) if (entry.getValue()) { + val player = entry.getKey(); + + if (sendVelocity) thisVelocityPacket.sendPacket(player); + thisTeleportPacket.sendPacket(player); + } + } + } + } + + @Override + protected void performLook(final float yaw, final float pitch) { + if (visible) { + WrapperPlayServerEntityLook thisLookPacket; + if ((thisLookPacket = lookPacket) == null) { + lookPacket = thisLookPacket = new WrapperPlayServerEntityLook(); + thisLookPacket.setEntityID(entityId); + } + + thisLookPacket.setYaw(yaw); + thisLookPacket.setPitch(pitch); + thisLookPacket.setOnGround(isOnGround()); + + for (val entry : players.entrySet()) if (entry.getValue()) thisLookPacket.sendPacket(entry.getKey()); + } + } + + /////////////////////////////////////////////////////////////////////////// + // Metadata + /////////////////////////////////////////////////////////////////////////// + + /** + * Sends metadata to all players seeing this entity creating packet if it has not yet been initialized. + */ + @Override + protected void sendMetadata() { + if (visible) { + final WrappedDataWatcher thisMetadata; + if ((thisMetadata = metadata) == null) return; + + actualizeMetadataPacket(thisMetadata); + + for (val entry : players.entrySet()) if (entry.getValue()) metadataPacket.sendPacket(entry.getKey()); + } + } + + /////////////////////////////////////////////////////////////////////////// + // Rendering + /////////////////////////////////////////////////////////////////////////// + + @Override + protected void render(final Player player) { + actualizeSpawnPacket(); + actualizeMetadataPacket(); + performSpawnNoChecks(player); + + players.put(player, true); + } + + @Override + protected void unrender(final Player player) { + performDespawnNoChecks(player); + + players.put(player, false); + } + + /////////////////////////////////////////////////////////////////////////// + // Visibility + /////////////////////////////////////////////////////////////////////////// + + @Override + public void setVisible(final boolean visible) { + if (this.visible == visible) return; + + this.visible = visible; + + if (visible) spawn(); + else despawn(); + } + + @Override + public void remove() { + despawn(); + + players.clear(); + } +} diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteraction.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteraction.java similarity index 87% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteraction.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteraction.java index 9455bb494..7ffbb6639 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteraction.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteraction.java @@ -4,6 +4,7 @@ import lombok.experimental.FieldDefaults; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.util.Vector; +import ru.progrm_jarvis.minecraft.commons.nms.NmsUtil; public interface FakeEntityInteraction { @@ -19,7 +20,7 @@ public interface FakeEntityInteraction { Hand getHand(); - static FakeEntityInteraction interact(final int entityId, @NonNull final Hand hand) { + static FakeEntityInteraction interact(final int entityId, final @NonNull Hand hand) { return new InteractInteraction(entityId, hand); } @@ -27,12 +28,12 @@ static FakeEntityInteraction attack(final int entityId) { return new AttackInteraction(entityId); } - static FakeEntityInteraction exactInteract(final int entityId, @NonNull final Hand hand, + static FakeEntityInteraction exactInteract(final int entityId, final @NonNull Hand hand, final int x, final int y, final int z) { return new ExactInteractInteraction(entityId, hand, x, y, z); } - static FakeEntityInteraction exactInteract(final int entityId, @NonNull final Hand hand, + static FakeEntityInteraction exactInteract(final int entityId, final @NonNull Hand hand, final Vector vector) { // TODO: 20.11.2018 Check if right return new ExactInteractInteraction(entityId, hand, vector.getBlockX(), vector.getBlockY(), vector.getBlockZ()); @@ -116,18 +117,19 @@ class ExactInteractInteraction implements FakeEntityInteraction { @Override public Type getType() { - return Type.EXACT_INTERACTION; + return Type.EXACT_INTERACT; } } @RequiredArgsConstructor enum Hand { - MAIN(EquipmentSlot.HAND), OFF(EquipmentSlot.OFF_HAND); + MAIN(EquipmentSlot.HAND), + OFF(NmsUtil.getVersion().getGeneration() > 8 ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND); @Getter final EquipmentSlot slot; } enum Type { - INTERACT, ATTACK, EXACT_INTERACTION + INTERACT, ATTACK, EXACT_INTERACT } } \ No newline at end of file diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteractionEvent.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteractionEvent.java similarity index 85% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteractionEvent.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteractionEvent.java index 492ea7664..3c89a6501 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteractionEvent.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteractionEvent.java @@ -21,7 +21,7 @@ public HandlerList getHandlers() { return handlerList; } - public FakeEntityInteractionEvent(@NonNull final Player who, @NonNull final InteractableFakeEntity entity) { + public FakeEntityInteractionEvent(final @NonNull Player who, final @NonNull InteractableFakeEntity entity) { super(who); this.entity = entity; diff --git a/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteractionHandler.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteractionHandler.java new file mode 100644 index 000000000..4937e0335 --- /dev/null +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteractionHandler.java @@ -0,0 +1,6 @@ +package ru.progrm_jarvis.minecraft.fakeentitylib.entity.behaviour; + +import ru.progrm_jarvis.minecraft.fakeentitylib.entity.management.FakeEntityManager; + +public interface FakeEntityInteractionHandler extends FakeEntityManager { +} diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/InteractableFakeEntity.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/InteractableFakeEntity.java similarity index 85% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/InteractableFakeEntity.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/InteractableFakeEntity.java index 588219ff0..887a9f5cc 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/InteractableFakeEntity.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/InteractableFakeEntity.java @@ -5,9 +5,5 @@ public interface InteractableFakeEntity extends FakeEntity { - int getEntityId(); - - boolean sendsEvents(); - void handleInteraction(Player player, FakeEntityInteraction interaction); } diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/ProtocolBasedFakeEntityInteractionHandler.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/ProtocolBasedFakeEntityInteractionHandler.java similarity index 65% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/ProtocolBasedFakeEntityInteractionHandler.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/ProtocolBasedFakeEntityInteractionHandler.java index f589ee84a..c65a1063f 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/ProtocolBasedFakeEntityInteractionHandler.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/ProtocolBasedFakeEntityInteractionHandler.java @@ -3,45 +3,59 @@ import com.comphenix.packetwrapper.WrapperPlayClientUseEntity; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.wrappers.EnumWrappers; -import com.google.common.base.Preconditions; import lombok.*; +import lombok.experimental.Delegate; import lombok.experimental.FieldDefaults; import org.bukkit.plugin.Plugin; +import ru.progrm_jarvis.minecraft.commons.util.shutdown.ShutdownHooks; +import ru.progrm_jarvis.minecraft.commons.util.shutdown.Shutdownable; import ru.progrm_jarvis.minecraft.fakeentitylib.entity.behaviour.FakeEntityInteraction.Hand; import ru.progrm_jarvis.minecraft.fakeentitylib.entity.management.FakeEntityManager; -import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Set; -import static ru.progrm_jarvis.minecraft.commons.util.hack.PreSuperCheck.beforeSuper; +import static com.google.common.base.Preconditions.checkNotNull; -@ToString -@EqualsAndHashCode(callSuper = true) +@ToString(onlyExplicitlyIncluded = true) @FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) -public class ProtocolBasedFakeEntityInteractionHandler

- extends PacketAdapter implements FakeEntityInteractionHandler { +public class ProtocolBasedFakeEntityInteractionHandler + extends PacketAdapter implements FakeEntityInteractionHandler { - P plugin; - Set entities; + final @NonNull ProtocolManager protocolManager; - public ProtocolBasedFakeEntityInteractionHandler(@NonNull final P plugin, final boolean concurrent) { + @ToString.Include @NonNull Plugin plugin; + @NonNull Set entities; + @NonNull Set entitiesView; + + @Delegate(types = Shutdownable.class) @NonNull ShutdownHooks shutdownHooks; + + public ProtocolBasedFakeEntityInteractionHandler(final @NonNull Plugin plugin, final boolean concurrent) { super( - beforeSuper(plugin, () -> Preconditions.checkNotNull(plugin, "plugin should not be null")), + checkNotNull(plugin, "plugin should not be null"), PacketType.Play.Client.USE_ENTITY ); + protocolManager = ProtocolLibrary.getProtocolManager(); + this.plugin = plugin; entities = concurrent ? FakeEntityManager.concurrentWeakEntitySet() : FakeEntityManager.weakEntitySet(); + entitiesView = Collections.unmodifiableSet(entities); - ProtocolLibrary.getProtocolManager().addPacketListener(this); + protocolManager.addPacketListener(this); + + shutdownHooks = (concurrent ? ShutdownHooks.createConcurrent(this) : ShutdownHooks.create(this)) + .add(() -> protocolManager.removePacketListener(this)) + .registerBukkitShutdownHook(plugin); } @Override - public P getBukkitPlugin() { + public Plugin getBukkitPlugin() { return plugin; } @@ -77,6 +91,8 @@ public void onPacketReceiving(final PacketEvent event) { break; } } + + event.setCancelled(true); } @Override @@ -85,13 +101,13 @@ public int managedEntitiesSize() { } @Override - public Collection getManagedEntities() { - return entities; + public boolean isManaged(final @NonNull E entity) { + return entities.contains(entity); } @Override - public Collection getManagedEntitiesCollection() { - return new ArrayList<>(entities); + public Collection getManagedEntities() { + return entities; } @Override diff --git a/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/AbstractSetBasedEntityManager.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/AbstractSetBasedEntityManager.java new file mode 100644 index 000000000..b8e6e5385 --- /dev/null +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/AbstractSetBasedEntityManager.java @@ -0,0 +1,85 @@ +package ru.progrm_jarvis.minecraft.fakeentitylib.entity.management; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.experimental.Delegate; +import lombok.experimental.FieldDefaults; +import org.bukkit.plugin.Plugin; +import ru.progrm_jarvis.minecraft.commons.util.shutdown.ShutdownHooks; +import ru.progrm_jarvis.minecraft.commons.util.shutdown.Shutdownable; +import ru.progrm_jarvis.minecraft.fakeentitylib.entity.FakeEntity; + +import javax.annotation.OverridingMethodsMustInvokeSuper; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +/** + * Simple abstract {@link FakeEntityManager} storing entities in a weak, optionally concurrent set. + * + * @param type of entities stored + */ +@ToString +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +public abstract class AbstractSetBasedEntityManager implements FakeEntityManager { + + @NonNull Plugin plugin; + @ToString.Exclude @NonNull Set entities; + @ToString.Exclude @NonNull Set entitiesView; + + @Delegate(types = Shutdownable.class) @NonNull ShutdownHooks shutdownHooks; + + public AbstractSetBasedEntityManager(final @NonNull Plugin plugin, final @NonNull Set entities) { + this.plugin = plugin; + this.entities = entities; + this.entitiesView = Collections.unmodifiableSet(entities); + + shutdownHooks = ShutdownHooks.createConcurrent(this) + .registerBukkitShutdownHook(plugin); + } + + /** + * Constructs a new AbstractSetBasedEntityManager based on weak set with optional concurrency + * + * @param plugin parent plugin of this manager + * @param concurrent whether or not this map should be thread-safe + */ + public AbstractSetBasedEntityManager(final @NonNull Plugin plugin, final boolean concurrent) { + this(plugin, concurrent ? FakeEntityManager.concurrentWeakEntitySet() : FakeEntityManager.weakEntitySet()); + } + + @Override + public Plugin getBukkitPlugin() { + return plugin; + } + + @Override + public int managedEntitiesSize() { + return entities.size(); + } + + @Override + public boolean isManaged(final @NonNull E entity) { + return entities.contains(entity); + } + + @Override + public Collection getManagedEntities() { + return entitiesView; + } + + @Override + @OverridingMethodsMustInvokeSuper + public void manageEntity(final @NonNull E entity) { + entities.add(entity); + } + + @Override + @OverridingMethodsMustInvokeSuper + public void unmanageEntity(final @NonNull E entity) { + entities.remove(entity); + } +} diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/FakeEntityManager.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/FakeEntityManager.java similarity index 62% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/FakeEntityManager.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/FakeEntityManager.java index 3ad4a514a..ebf1ce191 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/FakeEntityManager.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/FakeEntityManager.java @@ -1,9 +1,10 @@ package ru.progrm_jarvis.minecraft.fakeentitylib.entity.management; import lombok.NonNull; -import org.bukkit.plugin.Plugin; -import ru.progrm_jarvis.minecraft.commons.util.concurrent.ConcurrentCollections; +import ru.progrm_jarvis.javacommons.annotation.DontOverrideEqualsAndHashCode; +import ru.progrm_jarvis.javacommons.collection.concurrent.ConcurrentCollections; import ru.progrm_jarvis.minecraft.commons.plugin.BukkitPluginContainer; +import ru.progrm_jarvis.minecraft.commons.util.shutdown.Shutdownable; import ru.progrm_jarvis.minecraft.fakeentitylib.entity.FakeEntity; import java.util.*; @@ -11,52 +12,54 @@ /** * Basic class for entity management providing basic general functionality for it. * - * @param

type of parent plugin * @param type of managed entity * * @implSpec its highly recommended (read necessary) for implementations * to store managed entities weakly so that un-managing entity manually is not required * as if there are no strong references on it the GC should collect it. */ -public interface FakeEntityManager

extends BukkitPluginContainer

{ +@DontOverrideEqualsAndHashCode("EntityManagers are not data objects") +public interface FakeEntityManager extends BukkitPluginContainer, Shutdownable { /** * Creates a new weak {@link Set} valid (and recommended) for storing entities * - * @param type of entities stored + * @param type of value stored * @return new weak {@link Set} for storing entities */ - static Set weakEntitySet() { + static Set weakEntitySet() { return Collections.newSetFromMap(new WeakHashMap<>()); } /** * Creates a new weak concurrent {@link Set} valid (and recommended) for storing entities * - * @param type of entities stored + * @param type of value stored * @return new weak concurrent {@link Set} for storing entities */ - static Set concurrentWeakEntitySet() { + static Set concurrentWeakEntitySet() { return ConcurrentCollections.concurrentSetFromMap(new WeakHashMap<>()); } /** * Creates a new weak {@link Set} valid (and recommended) for storing entities * - * @param type of entities stored + * @param type of key + * @param type of value * @return new weak {@link Set} for storing entities */ - static Map weakEntityMap() { + static Map weakEntityMap() { return new WeakHashMap<>(); } /** * Creates a new weak concurrent {@link Map} valid (and recommended) for storing entities * - * @param type of entities stored + * @param type of key + * @param type of value * @return new weak concurrent {@link Map} for storing entities */ - static Map concurrentWeakEntityMap() { + static Map concurrentWeakEntityMap() { return ConcurrentCollections.concurrentMap(new WeakHashMap<>()); } @@ -72,22 +75,10 @@ static Map concurrentWeakEntityMap() { * * @return all entities managed by this manager * - * @apiNote implementations may return their real backend collection - * modifications to which may have side-effect on this manager so it's a good practice - * to copy method call result into a new collection or use + * @apiNote the returned collection is immutable and will prohibit any attempts to modify its contents */ Collection getManagedEntities(); - /** - * Gets a side-effect-less collection of all entities managed by this manager. - * - * @return collection of all entities managed by this manager - * - * @apiNote side-effect-less collection means that modifications to it - * will not have any effect on containment of this entities in the manager - */ - Collection getManagedEntitiesCollection(); - /** * Enables management of specified entity by this manager. * @@ -107,4 +98,27 @@ static Map concurrentWeakEntityMap() { * as this interface specification recommends storing entities weakly. */ void unmanageEntity(@NonNull E entity); + + /** + * Retrieves whether or not the specified entity is managed by this manager. + * + * @param entity entity to check + * @return {@code true} if this entity manager manages the specified entity and {@code false} otherwise + */ + boolean isManaged(final @NonNull E entity); + + /** + * Removes the entity managed by this manager. + * This is a logical equivalent of calling {@link #unmanageEntity(FakeEntity)} and {@link FakeEntity#remove()} + * + * @param entity entity to remove + * + * @apiNote if the entity is not managed by this manager then no exception should be thrown + * but the removal should happen + */ + default void remove(@NonNull E entity) { + unmanageEntity(entity); + + entity.remove(); + } } diff --git a/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/FakeEntityManagerGroup.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/FakeEntityManagerGroup.java new file mode 100644 index 000000000..d222e8d04 --- /dev/null +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/FakeEntityManagerGroup.java @@ -0,0 +1,77 @@ +package ru.progrm_jarvis.minecraft.fakeentitylib.entity.management; + +import com.google.common.collect.ImmutableList; +import lombok.*; +import lombok.experimental.FieldDefaults; +import org.bukkit.plugin.Plugin; +import ru.progrm_jarvis.minecraft.commons.player.registry.PlayerRegistryRegistration; +import ru.progrm_jarvis.minecraft.fakeentitylib.entity.FakeEntity; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; +import java.util.function.BiFunction; + + +/** + * Facade grouping multiple {@link FakeEntityManager}s into a single one. + * Its general methods delegate the calls to each of the managers. + * + * @param type of managed entity + */ +@ToString +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +@PlayerRegistryRegistration(PlayerRegistryRegistration.Policy.MANUAL) +public class FakeEntityManagerGroup extends AbstractSetBasedEntityManager { + + @NonNull Collection> managers; + + public FakeEntityManagerGroup(final @NonNull Plugin plugin, + final @NonNull Set entities, + final @NonNull Collection, + ? extends FakeEntityManager>> managerCreators) { + super(plugin, entities); + + //noinspection unchecked + managers = ImmutableList.copyOf(managerCreators.stream() + .map(managerCreator -> managerCreator.apply(plugin, entities)) + .toArray(FakeEntityManager[]::new) + ); + } + + @SafeVarargs + public FakeEntityManagerGroup(final @NonNull Plugin plugin, + final @NonNull Set entities, + final @NonNull BiFunction, + ? extends FakeEntityManager>... managerCreators) { + this(plugin, entities, Arrays.asList(managerCreators)); + } + + @Override + public void manageEntity(final @NonNull E entity) { + super.manageEntity(entity); + + for (val manager : managers) manager.manageEntity(entity); + } + + @Override + public void unmanageEntity(final @NonNull E entity) { + super.unmanageEntity(entity); + + for (val manager : managers) manager.unmanageEntity(entity); + } + + @Override + public void remove(final @NonNull E entity) { + for (val manager : managers) manager.remove(entity); + + entity.remove(); + } + + @Override + public void shutdown() { + for (val manager : managers) manager.shutdown(); + + super.shutdown(); + } +} diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/observer/FakeEntityObserver.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/observer/FakeEntityObserver.java similarity index 66% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/observer/FakeEntityObserver.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/observer/FakeEntityObserver.java index 34256726b..0f934feb5 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/observer/FakeEntityObserver.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/observer/FakeEntityObserver.java @@ -6,9 +6,9 @@ /** * An object responsible for entity observation. + * Observation is the process of controlling the entity visibility for players. * - * @param

type of parent plugin * @param type of entity managed */ -public interface FakeEntityObserver

extends FakeEntityManager { +public interface FakeEntityObserver extends FakeEntityManager { } diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/observer/PeriodicFakeEntityObserver.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/observer/PeriodicFakeEntityObserver.java similarity index 61% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/observer/PeriodicFakeEntityObserver.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/observer/PeriodicFakeEntityObserver.java index bdb8c73dc..501f552d9 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/observer/PeriodicFakeEntityObserver.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/observer/PeriodicFakeEntityObserver.java @@ -2,17 +2,19 @@ import lombok.*; import lombok.experimental.FieldDefaults; -import org.bukkit.Bukkit; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; +import ru.progrm_jarvis.minecraft.commons.schedule.task.AbstractSchedulerRunnable; +import ru.progrm_jarvis.minecraft.commons.util.shutdown.ShutdownHooks; import ru.progrm_jarvis.minecraft.fakeentitylib.entity.ObservableFakeEntity; import ru.progrm_jarvis.minecraft.fakeentitylib.entity.management.AbstractSetBasedEntityManager; -import javax.annotation.Nonnull; -import java.util.*; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; @@ -20,17 +22,18 @@ import java.util.function.Supplier; import static com.google.common.base.Preconditions.checkArgument; +import static ru.progrm_jarvis.minecraft.commons.event.FluentBukkitEvents.on; import static ru.progrm_jarvis.minecraft.commons.util.hack.PreSuperCheck.beforeSuper; @ToString -@EqualsAndHashCode(callSuper = true) @FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) -public class PeriodicFakeEntityObserver

- extends AbstractSetBasedEntityManager implements FakeEntityObserver { +public class PeriodicFakeEntityObserver + extends AbstractSetBasedEntityManager implements FakeEntityObserver { Set tasks = new HashSet<>(); Lock lock = new ReentrantLock(); + boolean global; long interval; boolean async; int minEntitiesForNewThread; @@ -39,16 +42,17 @@ public class PeriodicFakeEntityObserver

> entitiesSetSupplier; @Builder - public PeriodicFakeEntityObserver(@Nonnull final P plugin, final boolean concurrent, - final long interval, final boolean async, + public PeriodicFakeEntityObserver(final @NonNull Plugin plugin, final boolean concurrent, + boolean global, final long interval, final boolean async, final int minEntitiesForNewThread, final int maxThreads, - @NonNull final Supplier> entitiesSetSupplier) { + final @NonNull Supplier> entitiesSetSupplier) { super(plugin, beforeSuper(concurrent, () -> checkArgument(interval > 0, "interval should be positive"), () -> checkArgument(minEntitiesForNewThread > 0, "minEntitiesForNewThread should be positive"), () -> checkArgument(maxThreads > 0, "maxThreads should be positive") )); + this.global = global; this.interval = interval; this.async = async; this.minEntitiesForNewThread = minEntitiesForNewThread; @@ -56,12 +60,39 @@ public PeriodicFakeEntityObserver(@Nonnull final P plugin, final boolean concurr this.entitiesSetSupplier = entitiesSetSupplier; - Bukkit.getPluginManager().registerEvents(new Listener() { - @EventHandler - public void onPluginDisable(final PluginDisableEvent event) { - if (event.getPlugin() == plugin) shutdown(); + final ShutdownHooks shutdownHooks; + (shutdownHooks = this.shutdownHooks).add(() -> { + lock.lock(); + try { + for (val task : tasks) task.cancel(); + } finally { + lock.unlock(); } - }, plugin); + }); + + if (global) shutdownHooks + .add(on(PlayerJoinEvent.class) + .plugin(plugin) + .register(event -> addPlayer(event.getPlayer()))::shutdown) + .add(on(PlayerQuitEvent.class) + .plugin(plugin) + .register(event -> removePlayer(event.getPlayer()))::shutdown); + + shutdownHooks.add(on(PlayerRespawnEvent.class) + .plugin(plugin) + .register(event -> { + final Player player; + removePlayer(player = event.getPlayer()); + addPlayer(player); + })::shutdown); + } + + protected void addPlayer(final @NonNull Player player) { + for (val entity : entities) entity.addPlayer(player); + } + + protected void removePlayer(final @NonNull Player player) { + for (val entity : entities) entity.removePlayer(player); } protected RedrawEntitiesRunnable getRedrawEntitiesRunnable() { @@ -72,7 +103,7 @@ protected RedrawEntitiesRunnable getRedrawEntitiesRunnable() { RedrawEntitiesRunnable minRunnable = null; Integer minEntitiesInRunnable = null; for (val task : tasks) { - val taskEntitiesSize = task.entities.size(); + val taskEntitiesSize = task.size(); // if task has no even reached its entity minimum then use it if (taskEntitiesSize < minEntitiesForNewThread) return task; @@ -102,39 +133,36 @@ protected RedrawEntitiesRunnable newRedrawEntitiesRunnable() { } @Override - public void manageEntity(@NonNull final E entity) { + public void manageEntity(final @NonNull E entity) { super.manageEntity(entity); getRedrawEntitiesRunnable().addEntity(entity); } @Override - public void unmanageEntity(@NonNull final E entity) { + public void unmanageEntity(final @NonNull E entity) { super.unmanageEntity(entity); lock.lock(); try { val iterator = tasks.iterator(); - for (val task : tasks) if (task.removeEntity(entity)) { - if (task.entities.size() == 0) iterator.remove(); + while (iterator.hasNext()) { + val task = iterator.next(); + if (task.removeEntity(entity)) { + if (task.isEmpty()) { + iterator.remove(); + task.cancel(); + } - break; + break; + } } } finally { lock.unlock(); } } - protected void shutdown() { - lock.lock(); - try { - for (val task : tasks) task.cancel(); - } finally { - lock.unlock(); - } - } - @ToString @EqualsAndHashCode(callSuper = true) - protected class RedrawEntitiesRunnable extends BukkitRunnable { + protected class RedrawEntitiesRunnable extends AbstractSchedulerRunnable { protected final Collection entities = entitiesSetSupplier.get(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); @@ -143,9 +171,14 @@ public int size() { return entities.size(); } + public boolean isEmpty() { + return entities.isEmpty(); + } + public void addEntity(final E entity) { lock.writeLock().lock(); try { + if (global && entity.isGlobal()) entity.addOnlinePlayers(); entities.add(entity); } finally { lock.writeLock().unlock(); diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/Structure.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/Structure.java similarity index 95% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/Structure.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/Structure.java index 5ddebb4f0..2bfb59f75 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/Structure.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/Structure.java @@ -87,14 +87,14 @@ default void accept(final Structure.Element element) { update(element); } - default Updater alsoThen(@NonNull final Updater updater) { + default Updater alsoThen(final @NonNull Updater updater) { return element -> { update(element); updater.update(element); }; } - default Updater alsoBefore(@NonNull final Updater updater) { + default Updater alsoBefore(final @NonNull Updater updater) { return element -> { update(element); updater.update(element); diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/StructureDescriptor.java b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/StructureDescriptor.java similarity index 89% rename from fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/StructureDescriptor.java rename to fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/StructureDescriptor.java index 6b0fcc39b..0d4d18267 100644 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/StructureDescriptor.java +++ b/fake-entity-lib/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/StructureDescriptor.java @@ -9,8 +9,8 @@ import com.google.gson.JsonElement; import com.google.gson.annotations.SerializedName; import com.google.gson.stream.JsonReader; -import gnu.trove.map.TIntObjectMap; -import gnu.trove.map.hash.TIntObjectHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.*; import lombok.experimental.FieldDefaults; import org.bukkit.Material; @@ -31,21 +31,20 @@ */ @Value @Builder -@FieldDefaults(level = AccessLevel.PRIVATE) public class StructureDescriptor { - @Singular private ImmutableList elements; + @Singular ImmutableList elements; - @Builder.Default private int frames = 0; + @Builder.Default int frames = 0; /** * Keyframes of the structure. */ - private TIntObjectMap keyframes; + Int2ObjectMap keyframes; private static final Gson gson = new Gson(); - public static StructureDescriptor from(@NonNull final JsonRepresentation jsonRepresentation) { + public static StructureDescriptor from(final @NonNull JsonRepresentation jsonRepresentation) { val elementNames = new ArrayList(); val elementList = new ArrayList(); jsonRepresentation.getElements().forEach((name, element) -> { @@ -56,7 +55,7 @@ public static StructureDescriptor from(@NonNull final JsonRepresentation jsonRep val descriptor = builder().elements(elementList); // add elements to descriptor val keyframesList = jsonRepresentation.getOrderedKeyframes(); if (!keyframesList.isEmpty()) { - val keyframes = new TIntObjectHashMap(); + val keyframes = new Int2ObjectOpenHashMap(); for (val keyframe : keyframesList) keyframes .put(keyframe.tick, keyframe.toFrameUpdaterByElementNames(elementNames)); @@ -67,24 +66,24 @@ public static StructureDescriptor from(@NonNull final JsonRepresentation jsonRep return descriptor.build(); } - public static StructureDescriptor fromJson(@NonNull final String json) { + public static StructureDescriptor fromJson(final @NonNull String json) { return from(gson.fromJson(json, JsonRepresentation.class)); } - public static StructureDescriptor fromJson(@NonNull final Reader jsonReader) { + public static StructureDescriptor fromJson(final @NonNull Reader jsonReader) { return from(gson.fromJson(jsonReader, JsonRepresentation.class)); } - public static StructureDescriptor fromJson(@NonNull final JsonReader jsonReader) { + public static StructureDescriptor fromJson(final @NonNull JsonReader jsonReader) { return from(gson.fromJson(jsonReader, JsonRepresentation.class)); } - public static StructureDescriptor fromJson(@NonNull final JsonElement jsonElement) { + public static StructureDescriptor fromJson(final @NonNull JsonElement jsonElement) { return from(gson.fromJson(jsonElement, JsonRepresentation.class)); } @SneakyThrows - public static StructureDescriptor fromJson(@NonNull final File jsonFile) { + public static StructureDescriptor fromJson(final @NonNull File jsonFile) { try (val reader = new BufferedReader(new FileReader(jsonFile))) { return fromJson(reader); } @@ -106,7 +105,7 @@ public static class FastFrameUpdater implements FrameUpdater { int[] ids; Structure.Element.Updater[] updaters; - public static FrameUpdater from(@NonNull final int[] ids, @NonNull final Structure.Element.Updater[] updaters) { + public static FrameUpdater from(final @NonNull int[] ids, final @NonNull Structure.Element.Updater[] updaters) { val idsLength = ids.length; Preconditions.checkArgument(idsLength == updaters.length, "ids length should be equal to updaters length"); @@ -149,9 +148,10 @@ public BiMap getElements() { } public List getOrderedKeyframes() { - return Arrays.stream(keyframes) + return ImmutableList.copyOf(Arrays.stream(keyframes) .sorted(Comparator.comparing(keyframe -> keyframe.tick)) - .collect(ImmutableList.toImmutableList()); + .toArray(Keyframe[]::new) + ); } @Data @@ -204,7 +204,7 @@ public static final class Element { Translation translation; - public static Structure.Element.Size sizeFromName(@NonNull final String sizeName) { + public static Structure.Element.Size sizeFromName(final @NonNull String sizeName) { switch (sizeName) { case "small": return Structure.Element.Size.SMALL; case "medium": return Structure.Element.Size.MEDIUM; @@ -240,7 +240,7 @@ public static final class Keyframe { @SerializedName("ontick") int tick; Element[] objects; - public FrameUpdater toFrameUpdaterByElements(@NonNull final List elements) { + public FrameUpdater toFrameUpdaterByElements(final @NonNull List elements) { final Map elementMap = Arrays.stream(objects) .collect(Collectors.toMap(element -> { val index = elements.indexOf(element); @@ -259,7 +259,7 @@ public FrameUpdater toFrameUpdaterByElements(@NonNull final List elemen .toArray(Structure.Element.Updater[]::new)); } - public FrameUpdater toFrameUpdaterByElementNames(@NonNull final List elementNames) { + public FrameUpdater toFrameUpdaterByElementNames(final @NonNull List elementNames) { final Map elementMap = Arrays.stream(objects) .collect(Collectors.toMap(element -> { val index = elementNames.indexOf(element.customName); diff --git a/fake-entity/src/test/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/StructureDescriptorTest.java b/fake-entity-lib/src/test/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/StructureDescriptorTest.java similarity index 97% rename from fake-entity/src/test/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/StructureDescriptorTest.java rename to fake-entity-lib/src/test/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/StructureDescriptorTest.java index 0bfca33a6..149df2bdd 100644 --- a/fake-entity/src/test/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/StructureDescriptorTest.java +++ b/fake-entity-lib/src/test/java/ru/progrm_jarvis/minecraft/fakeentitylib/misc/structure/StructureDescriptorTest.java @@ -63,7 +63,7 @@ void testFromJson() { assertEquals(1, structureDescriptor.getKeyframes().size()); } - private File getFile(@NonNull final String fileName) { + private File getFile(final @NonNull String fileName) { return new File(getClass().getResource(fileName).getFile()); } } \ No newline at end of file diff --git a/fake-entity/src/test/resources/entity_descriptor_1.json b/fake-entity-lib/src/test/resources/entity_descriptor_1.json similarity index 100% rename from fake-entity/src/test/resources/entity_descriptor_1.json rename to fake-entity-lib/src/test/resources/entity_descriptor_1.json diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractBasicFakeEntity.java b/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractBasicFakeEntity.java deleted file mode 100644 index c66c453e7..000000000 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/AbstractBasicFakeEntity.java +++ /dev/null @@ -1,163 +0,0 @@ -package ru.progrm_jarvis.minecraft.fakeentitylib.entity; - -import com.comphenix.protocol.wrappers.WrappedDataWatcher; -import com.comphenix.protocol.wrappers.WrappedWatchableObject; -import lombok.*; -import lombok.experimental.FieldDefaults; -import org.bukkit.Location; -import org.bukkit.entity.Player; - -import javax.annotation.Nullable; -import java.util.*; - -/** - * Base for most common implementations of {@link BasicFakeEntity} containing player logic base. - */ -@ToString -@EqualsAndHashCode(callSuper = false) -@FieldDefaults(level = AccessLevel.PROTECTED) -public abstract class AbstractBasicFakeEntity extends AbstractPlayerContainingFakeEntity implements BasicFakeEntity { - - /** - * Metadata of this fake entity - */ - @Nullable @Getter WrappedDataWatcher metadata; - - public AbstractBasicFakeEntity(final boolean global, final int viewDistance, - @NonNull final Location location, - @NonNull final Map players, - @Nullable final WrappedDataWatcher metadata) { - super(viewDistance, global, location, players); - - this.metadata = metadata; - } - - /////////////////////////////////////////////////////////////////////////// - // Metadata - /////////////////////////////////////////////////////////////////////////// - - /** - * Sends metadata to all players seeing this entity creating packet if it has not yet been initialized. - */ - protected abstract void sendMetadata(); - - @Override - public void setMetadata(@NonNull final WrappedDataWatcher metadata) { - this.metadata = metadata.deepClone(); - - sendMetadata(); - } - - @Override - public void setMetadata(@NonNull final List metadata) { - this.metadata = new WrappedDataWatcher(metadata); - - sendMetadata(); - } - - @Override - public void setMetadata(@NonNull final Collection metadata) { - setMetadata(new ArrayList<>(metadata)); - - sendMetadata(); - } - - @Override - public void setMetadata(@NonNull final WrappedWatchableObject... metadata) { - setMetadata(Arrays.asList(metadata)); - - sendMetadata(); - } - - @Override - public void addMetadata(final List metadata) { - if (this.metadata == null) this.metadata = new WrappedDataWatcher(metadata); - else for (val metadatum : metadata) this.metadata.setObject(metadatum.getIndex(), metadatum); - - sendMetadata(); - } - - @Override - public void addMetadata(final Collection metadata) { - if (this.metadata == null) this.metadata = new WrappedDataWatcher(new ArrayList<>(metadata)); - else for (val metadatum : metadata) this.metadata.setObject(metadatum.getIndex(), metadatum); - - sendMetadata(); - } - - @Override - public void addMetadata(final WrappedWatchableObject... metadata) { - if (this.metadata == null) this.metadata = new WrappedDataWatcher(Arrays.asList(metadata)); - else for (val metadatum : metadata) this.metadata.setObject(metadatum.getIndex(), metadatum); - - sendMetadata(); - } - - @Override - public void removeMetadata(final Iterable indexes) { - if (metadata == null) return; - - for (val index : indexes) metadata.remove(index); - - sendMetadata(); - } - - @Override - public void removeMetadata(final int... indexes) { - if (metadata == null) return; - - for (val index : indexes) metadata.remove(index); - - sendMetadata(); - } - - /////////////////////////////////////////////////////////////////////////// - // Movement - /////////////////////////////////////////////////////////////////////////// - /** - * Performs the movement of this living fake entity by given deltas and yaw and pitch specified - * not performing any checks such as 8-block limit of deltas or angle minimization. - * - * @param dx delta on X-axis - * @param dy delta on Y-axis - * @param dz delta on Z-axis - * @param yaw new yaw - * @param pitch new pitch - */ - protected abstract void performMove(final double dx, final double dy, final double dz, - final float yaw, final float pitch); - - /** - * Performs the teleportation of this living fake entity to given coordinates changing yaw and pitch - * not performing any checks such as using movement for less than 8-block deltas or angle minimization. - * - * @param x new location on X-axis - * @param y new location on Y-axis - * @param z new location on Z-axis - * @param yaw new yaw - * @param pitch new pitch - */ - protected abstract void performTeleportation(final double x, final double y, final double z, - final float yaw, final float pitch); - - @Override - public void teleport(final double x, final double y, final double z, final float yaw, final float pitch) { - final double dx = x - location.getX(), dy = y - location.getY(), dz = z - location.getZ(); - - if (dx > 8 || dy > 8 || dz > 8) performMove(dx, dy, dz, yaw, pitch); - else performTeleportation(x, y, z, yaw, pitch); - } - - @Override - public void move(final double dx, final double dy, final double dz, final float dYaw, final float dPitch) { - if (dx > 8 || dy > 8 || dz > 8) performMove(dx, dy, dz, location.getYaw() + dYaw, location.getPitch() + dPitch); - else performTeleportation( - location.getX() + dx, location.getY() + dy, location.getZ() + dz, - location.getYaw() + dYaw, location.getPitch() + dPitch - ); - } - - /////////////////////////////////////////////////////////////////////////// - // ASPECTS - /////////////////////////////////////////////////////////////////////////// -} diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/ArmorStandBlockItem.java b/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/ArmorStandBlockItem.java deleted file mode 100644 index 52261a135..000000000 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/ArmorStandBlockItem.java +++ /dev/null @@ -1,189 +0,0 @@ -package ru.progrm_jarvis.minecraft.fakeentitylib.entity; - -import com.comphenix.packetwrapper.WrapperPlayServerEntityEquipment; -import com.comphenix.protocol.wrappers.EnumWrappers; -import com.comphenix.protocol.wrappers.Vector3F; -import com.comphenix.protocol.wrappers.WrappedDataWatcher; -import com.comphenix.protocol.wrappers.WrappedWatchableObject; -import lombok.AccessLevel; -import lombok.NonNull; -import lombok.experimental.FieldDefaults; -import lombok.val; -import org.bukkit.Location; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import ru.progrm_jarvis.minecraft.nmsutils.NmsUtil; -import ru.progrm_jarvis.minecraft.nmsutils.metadata.MetadataGenerator; -import ru.progrm_jarvis.minecraft.nmsutils.metadata.MetadataGenerator.ArmorStand; -import ru.progrm_jarvis.minecraft.nmsutils.metadata.MetadataGenerator.Entity; - -import javax.annotation.Nullable; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -import static ru.progrm_jarvis.minecraft.nmsutils.metadata.MetadataGenerator.ArmorStand.armorStandFlags; -import static ru.progrm_jarvis.minecraft.nmsutils.metadata.MetadataGenerator.ArmorStand.headRotation; -import static ru.progrm_jarvis.minecraft.nmsutils.metadata.MetadataGenerator.Entity.entityFlags; - -/** - * A fake small (or very small) movable block-item which can be normally rotated over all axises - * and have floating point coordinates. This block is displayed as an item on head of invisible armor stand. - */ -@FieldDefaults(level = AccessLevel.PROTECTED) -public class ArmorStandBlockItem extends SimpleLivingFakeEntity { - - /** - * Rotation of this block - */ - @Nullable Vector3F rotation; - - /** - * Item displayed by this block - */ - @NonNull ItemStack item; - - /** - * Packet used for displaying this block's displayed item - */ - WrapperPlayServerEntityEquipment equipmentPacket; - - /** - * Initializes a newly created armor stand block-item from parameters given. - * - * @param uuid unique entity id of this block-item entity - * @param playersMap map to be used as backend for this block-item entity - * @param global whether this block-item is global (the value returned by {@link #isGlobal()}) - * @param visible whether this block-item is initially be visible - * @param viewDistance view distance of this block-item - * @param location location of this block-item - * @param rotation rotation of this block item - * @param small whether this block-item is small - * @param item item to be displayed by this block-item - */ - public ArmorStandBlockItem(@Nullable final UUID uuid, - final Map playersMap, - final boolean global, final int viewDistance, final boolean visible, - final Location location, @Nullable final Vector3F rotation, - final boolean small, @NonNull final ItemStack item) { - super( - NmsUtil.nextEntityId(), uuid, EntityType.ARMOR_STAND, - playersMap, global, viewDistance, visible, location, 0, null, createMetadata(rotation, small) - ); - - this.rotation = rotation; - - equipmentPacket = new WrapperPlayServerEntityEquipment(); - equipmentPacket.setEntityID(id); - equipmentPacket.setSlot(EnumWrappers.ItemSlot.HEAD); - equipmentPacket.setItem(this.item = item); - - // actual block-item position (head of armorstand) is one block higher than its coordinate so normalize it - yDelta = -1; - } - - /** - * Creates new armor stand block-item by parameters specified. - * - * @param concurrent whether created block-item supports concurrent modification of players related to it - * @param global whether created block-item is global (the value returned by {@link #isGlobal()}) - * @param viewDistance view distance of created block-item - * @param location location of created block-item - * @param rotation rotation of created block item - * @param small whether created block-item is small - * @param item item to be displayed by this block-item - * @return newly created armor stand block-item - */ - public static ArmorStandBlockItem create(final boolean concurrent, - final boolean global, final int viewDistance, final boolean visible, - final Location location, - final Vector3F rotation, final boolean small, - @NonNull final ItemStack item) { - return new ArmorStandBlockItem( - null, concurrent ? new ConcurrentHashMap<>() : new HashMap<>(), - global, viewDistance, visible, location, rotation, small, item - ); - } - - /** - * Creates valid metadata for armor stand block-item. - * - * @param rotation rotation of this block-item - * @param small whether this block-item is small - * @return created metadata object - */ - protected static WrappedDataWatcher createMetadata(@Nullable final Vector3F rotation, final boolean small) { - val metadata = new ArrayList(); - metadata.add(entityFlags(Entity.Flag.INVISIBLE)); - metadata.add(armorStandFlags(small - ? new ArmorStand.Flag[]{ArmorStand.Flag.SMALL, ArmorStand.Flag.MARKER} - : new ArmorStand.Flag[]{MetadataGenerator.ArmorStand.Flag.MARKER})); - if (rotation != null) metadata.add(headRotation(rotation)); - - return new WrappedDataWatcher(metadata); - } - - @Override - public void spawn() { - actualizeSpawnPacket(); - - for (val entry : players.entrySet()) if (entry.getValue()) { - val player = entry.getKey(); - - spawnPacket.sendPacket(player); - equipmentPacket.sendPacket(player); - } - } - - @Override - public void render(final Player player) { - actualizeSpawnPacket(); - spawnPacket.sendPacket(player); - equipmentPacket.sendPacket(player); - - players.put(player, true); - } - - /** - * Sets this blocks rotation to the one specified. - * - * @param rotation new rotation of this block - */ - public void setRotation(@NonNull final Vector3F rotation) { - addMetadata(headRotation(rotation)); - - this.rotation = rotation; - } - - /** - * Rotates this block by specified delta. This means that its current - * roll (x), pitch (y) and yaw (z) will each get incremented by those of delta specified. - * - * @param delta delta of rotation - */ - public void rotate(@NonNull final Vector3F delta) { - setRotation(rotation == null - ? new Vector3F(delta.getX(), delta.getY(), delta.getZ()) - : new Vector3F( - minimizeAngle(rotation.getX() + delta.getX()), - minimizeAngle(rotation.getY() + delta.getY()), - minimizeAngle(rotation.getZ() + delta.getZ()) - ) - ); - } - - /** - * Minimizes the angle so that it fits the interval of [-360; 360] keeping the actual rotation. - * This means removing 360 until the number is less than or equal to 360 - * or adding 360 until the number is bigger than or equal to -360. - * - * @param degrees non-minimized angle - * @return minimized angle - */ - public static float minimizeAngle(float degrees) { - while (degrees >= 360) degrees -= 360; - while (degrees <= -360) degrees += 360; - - return degrees; - } -} diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/SimpleLivingFakeEntity.java b/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/SimpleLivingFakeEntity.java deleted file mode 100644 index 93c3f4ffa..000000000 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/SimpleLivingFakeEntity.java +++ /dev/null @@ -1,371 +0,0 @@ -package ru.progrm_jarvis.minecraft.fakeentitylib.entity; - -import com.comphenix.packetwrapper.*; -import com.comphenix.protocol.wrappers.WrappedDataWatcher; -import lombok.*; -import lombok.experimental.FieldDefaults; -import lombok.experimental.NonFinal; -import org.bukkit.Location; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.util.Vector; -import ru.progrm_jarvis.minecraft.fakeentitylib.entity.aspect.annotation.WhenVisible; - -import javax.annotation.Nullable; -import java.util.Map; -import java.util.UUID; - -/** - * A simple living entity self-sustained for direct usage. - */ -@ToString -@EqualsAndHashCode(callSuper = true) -@FieldDefaults(level = AccessLevel.PROTECTED) -public class SimpleLivingFakeEntity extends AbstractBasicFakeEntity { - - /////////////////////////////////////////////////////////////////////////// - // Basic entity data - /////////////////////////////////////////////////////////////////////////// - - /** - * Unique entity ID by which it should be identified in all packets. - */ - final int id; // id should not be generated before all checks are performed - - /** - * This fake entity's UUID - */ - @Nullable final UUID uuid; // may be null as it is not required - - /** - * Type of this entity - */ - final EntityType type; // type of entity - - /////////////////////////////////////////////////////////////////////////// - // General for FakeEntity - /////////////////////////////////////////////////////////////////////////// - - /** - * PLayers related to this fake entity - */ - @NonNull final Map players; - - /** - * Whether or not this fake entity is global - */ - @Getter final boolean global; - - /** - * View distance for this entity or {@code -1} if none - */ - @Getter int viewDistance; - - /////////////////////////////////////////////////////////////////////////// - // Entity changing parameters - /////////////////////////////////////////////////////////////////////////// - - /** - * Location of this fake entity - */ - @NonNull @Getter final Location location; - - /** - * Head pitch of this fake entity - */ - float headPitch; - - /** - * Velocity of this fake entity - */ - @Nullable Vector velocity; - - /** - * Metadata of this fake entity - */ - @Nullable @Getter WrappedDataWatcher metadata; - - // packets should not be created before id is generated - - /** - * Packet used for spawning this fake entity - */ - WrapperPlayServerSpawnEntityLiving spawnPacket; - - /** - * Packet used for despawning this fake entity - */ - WrapperPlayServerEntityDestroy despawnPacket; - - // packets which should be initialized only when first needed - - /** - * Packet used for updating this fake entity's metadata - */ - @NonFinal WrapperPlayServerEntityMetadata metadataPacket; - - /** - * Packet used for moving this fake entity (not more than 8 blocks per axis) without modifying head rotation - */ - WrapperPlayServerRelEntityMove movePacket; - - /** - * Packet used for moving this fake entity (not more than 8 blocks per axis) and modifying head rotation - */ - WrapperPlayServerRelEntityMoveLook moveLookPacket; - - /** - * Packet used for teleporting this fake entity - */ - WrapperPlayServerEntityTeleport teleportPacket; - - /** - * Difference between the actual entity x and its visible value - */ - double xDelta, - /** - * Difference between the actual entity y and its visible value - */ - yDelta, - /** - * Difference between the actual entity z and its visible value - */ - zDelta; - - /** - * Difference between the actual entity yaw and its visible value - */ - float yawDelta = 0, - - /** - * Difference between the actual entity pitch and its visible value - */ - pitchDelta = 0, - - /** - * Difference between the actual entity head pitch and its visible value - */ - headPitchDelta = 0; - - @Builder - public SimpleLivingFakeEntity(final int entityId, @Nullable final UUID uuid, @NonNull final EntityType type, - @NonNull final Map players, - final boolean global, final int viewDistance, - boolean visible, - @NonNull final Location location, float headPitch, @Nullable final Vector velocity, - @Nullable final WrappedDataWatcher metadata) { - super(global, viewDistance, location, players, metadata); - - // setup fields - - this.id = entityId; - this.uuid = uuid; - this.type = type; - - this.players = players; - this.global = global; - this.viewDistance = Math.max(-1, viewDistance); - this.visible = visible; - - this.location = location; - this.headPitch = headPitch; - this.velocity = velocity; - - this.metadata = metadata; - - // setup packets - - spawnPacket = new WrapperPlayServerSpawnEntityLiving(); - spawnPacket.setEntityID(id); - spawnPacket.setType(type); - if (uuid != null) spawnPacket.setUniqueId(uuid); - - despawnPacket = new WrapperPlayServerEntityDestroy(); - despawnPacket.setEntityIds(new int[]{id}); - } - - /** - * Spawns the entity for player without performing any checks - * such as player containment checks or spawn packet actualization. - * - * @param player player to whom to spawn this entity - */ - protected void performSpawnNoChecks(final Player player) { - spawnPacket.sendPacket(player); - } - - /** - * Despawns the entity for player without performing any checks - * such as player containment checks or spawn packet actualization. - * - * @param player player to whom to despawn this entity - */ - protected void performDespawnNoChecks(final Player player) { - despawnPacket.sendPacket(player); - } - - protected void actualizeSpawnPacket() { - spawnPacket.setX(location.getX() + xDelta); - spawnPacket.setY(location.getY() + yDelta); - spawnPacket.setZ(location.getZ() + zDelta); - - spawnPacket.setPitch(location.getPitch() + pitchDelta); - spawnPacket.setYaw(location.getYaw() + yawDelta); - spawnPacket.setHeadPitch(headPitch + headPitchDelta); - - if (velocity != null) { - spawnPacket.setVelocityX(velocity.getX()); - spawnPacket.setVelocityY(velocity.getY()); - spawnPacket.setVelocityZ(velocity.getZ()); - } else { - spawnPacket.setVelocityX(0); - spawnPacket.setVelocityY(0); - spawnPacket.setVelocityZ(0); - } - - if (metadata != null) spawnPacket.setMetadata(metadata); - } - - /////////////////////////////////////////////////////////////////////////// - // Spawning / Despawning - /////////////////////////////////////////////////////////////////////////// - - @Override - @WhenVisible - public void spawn() { - actualizeSpawnPacket(); - - for (val entry : players.entrySet()) if (entry.getValue()) performSpawnNoChecks(entry.getKey()); - } - - @Override - @WhenVisible - public void despawn() { - for (val entry : players.entrySet()) if (entry.getValue()) performDespawnNoChecks(entry.getKey()); - } - - /////////////////////////////////////////////////////////////////////////// - // Movement - /////////////////////////////////////////////////////////////////////////// - - /** - * Performs the movement of this living fake entity by given deltas and yaw and pitch specified - * not performing any checks such as 8-block limit of deltas or angle minimization. - * - * @param dx delta on X-axis - * @param dy delta on Y-axis - * @param dz delta on Z-axis - * @param yaw new yaw - * @param pitch new pitch - */ - @Override - @WhenVisible - protected void performMove(final double dx, final double dy, final double dz, final float yaw, final float pitch) { - if (pitch == 0 && yaw == 0) { - if (movePacket == null) { - movePacket = new WrapperPlayServerRelEntityMove(); - movePacket.setEntityID(id); - } - - movePacket.setDx((int) (dx * 32 * 128)); - movePacket.setDy((int) (dy * 32 * 128)); - movePacket.setDz((int) (dz * 32 * 128)); - - for (val entry : players.entrySet()) if (entry.getValue()) movePacket.sendPacket(entry.getKey()); - } else { - if (moveLookPacket == null) { - moveLookPacket = new WrapperPlayServerRelEntityMoveLook(); - moveLookPacket.setEntityID(id); - - moveLookPacket.setDx((int) (dx * 32 * 128)); - moveLookPacket.setDy((int) (dy * 32 * 128)); - moveLookPacket.setDz((int) (dz * 32 * 128)); - moveLookPacket.setYaw(yaw); - moveLookPacket.setPitch(pitch); - - for (val entry : players.entrySet()) if (entry.getValue()) moveLookPacket.sendPacket(entry.getKey()); - } - } - } - - /** - * Performs the teleportation of this living fake entity to given coordinates changing yaw and pitch - * not performing any checks such as using movement for less than 8-block deltas or angle minimization. - * - * @param x new location on X-axis - * @param y new location on Y-axis - * @param z new location on Z-axis - * @param yaw new yaw - * @param pitch new pitch - */ - @Override - @WhenVisible - protected void performTeleportation(final double x, final double y, final double z, - final float yaw, final float pitch) { - if (teleportPacket == null) { - teleportPacket = new WrapperPlayServerEntityTeleport(); - teleportPacket.setEntityID(id); - } - - teleportPacket.setX(x + xDelta); - teleportPacket.setY(y + yDelta); - teleportPacket.setZ(z + zDelta); - teleportPacket.setYaw(yaw + yawDelta); - teleportPacket.setPitch(pitch + pitchDelta); - - for (val entry : players.entrySet()) if (entry.getValue()) teleportPacket.sendPacket(entry.getKey()); - } - - /////////////////////////////////////////////////////////////////////////// - // Metadata - /////////////////////////////////////////////////////////////////////////// - - /** - * Sends metadata to all players seeing this entity creating packet if it has not yet been initialized. - */ - @Override - @WhenVisible - protected void sendMetadata() { - if (metadata == null) return; - if (metadataPacket == null) { - metadataPacket = new WrapperPlayServerEntityMetadata(); - metadataPacket.setEntityID(id); - } - metadataPacket.setMetadata(metadata.getWatchableObjects()); - - for (val entry : players.entrySet()) if (entry.getValue()) metadataPacket.sendPacket(entry.getKey()); - } - - /////////////////////////////////////////////////////////////////////////// - // Rendering - /////////////////////////////////////////////////////////////////////////// - - @Override - public void render(final Player player) { - actualizeSpawnPacket(); - performSpawnNoChecks(player); - - players.put(player, true); - } - - @Override - public void unrender(final Player player) { - performDespawnNoChecks(player); - - players.put(player, false); - } - - /////////////////////////////////////////////////////////////////////////// - // Visibility - /////////////////////////////////////////////////////////////////////////// - - @Override - public void setVisible(final boolean visible) { - if (this.visible == visible) return; - - this.visible = visible; - - if (visible) spawn(); - else despawn(); - } -} diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/aspect/annotation/WhenVisible.java b/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/aspect/annotation/WhenVisible.java deleted file mode 100644 index 14ed92867..000000000 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/aspect/annotation/WhenVisible.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.progrm_jarvis.minecraft.fakeentitylib.entity.aspect.annotation; - -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import ru.progrm_jarvis.minecraft.fakeentitylib.entity.FakeEntity; - -import javax.annotation.Nullable; -import java.lang.annotation.*; - -/** - * Aspect annotation to perform method call only when the fake entity is visible. - * If the method returns an object then if an entity is invisible then {@code null} will be returned - * - * @apiNote can be used only on {@link FakeEntity} implementations - */ -// @Inherited NOT used because JVM will not inherit it for methods ¯\_(ツ)_/¯ -@Documented -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface WhenVisible { - - @Aspect - class AspectJ { - - @Around("execution(* *(..)) && @annotation(WhenVisible)") - @Nullable public Object around(final ProceedingJoinPoint joinPoint) throws Throwable { - return ((FakeEntity) joinPoint.getTarget()).isVisible() ? joinPoint.proceed() : null; - } - } -} \ No newline at end of file diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteractionHandler.java b/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteractionHandler.java deleted file mode 100644 index c5779281f..000000000 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/behaviour/FakeEntityInteractionHandler.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.progrm_jarvis.minecraft.fakeentitylib.entity.behaviour; - -import org.bukkit.plugin.Plugin; -import ru.progrm_jarvis.minecraft.fakeentitylib.entity.management.FakeEntityManager; - -public interface FakeEntityInteractionHandler

- extends FakeEntityManager { -} diff --git a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/AbstractSetBasedEntityManager.java b/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/AbstractSetBasedEntityManager.java deleted file mode 100644 index 1c7744ba0..000000000 --- a/fake-entity/src/main/java/ru/progrm_jarvis/minecraft/fakeentitylib/entity/management/AbstractSetBasedEntityManager.java +++ /dev/null @@ -1,77 +0,0 @@ -package ru.progrm_jarvis.minecraft.fakeentitylib.entity.management; - -import com.google.common.base.Preconditions; -import lombok.*; -import lombok.experimental.FieldDefaults; -import org.bukkit.plugin.Plugin; -import ru.progrm_jarvis.minecraft.fakeentitylib.entity.FakeEntity; - -import javax.annotation.Nonnull; -import javax.annotation.OverridingMethodsMustInvokeSuper; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Set; - -import static ru.progrm_jarvis.minecraft.commons.util.hack.PreSuperCheck.beforeSuper; - -/** - * Simple abstract {@link FakeEntityManager} storing entities in a weak, optionally concurrent set. - * - * @param

type of parent plugin - * @param type of entities stored - */ -@ToString -@EqualsAndHashCode -@RequiredArgsConstructor -@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) -public abstract class AbstractSetBasedEntityManager

- implements FakeEntityManager { - - P plugin; - Set entities; - - /** - * Constructs a new AbstractSetBasedEntityManager based on weak set with optional concurrency - * - * @param plugin parent plugin of this manager - * @param concurrent whether or not this map should be thread-safe - */ - public AbstractSetBasedEntityManager(@Nonnull final P plugin, final boolean concurrent) { - this( - beforeSuper(plugin, () -> Preconditions.checkNotNull(plugin, "plugin should be nonnull")), - concurrent ? FakeEntityManager.concurrentWeakEntitySet() : FakeEntityManager.weakEntitySet() - ); - } - - @Override - public P getBukkitPlugin() { - return plugin; - } - - @Override - public int managedEntitiesSize() { - return entities.size(); - } - - @Override - public Collection getManagedEntities() { - return entities; - } - - @Override - public Collection getManagedEntitiesCollection() { - return new ArrayList<>(entities); - } - - @Override - @OverridingMethodsMustInvokeSuper - public void manageEntity(@NonNull final E entity) { - entities.add(entity); - } - - @Override - @OverridingMethodsMustInvokeSuper - public void unmanageEntity(@NonNull final E entity) { - entities.remove(entity); - } -} diff --git a/commons/pom.xml b/lib-loader/pom.xml similarity index 56% rename from commons/pom.xml rename to lib-loader/pom.xml index 7244454be..4f7c4cacc 100644 --- a/commons/pom.xml +++ b/lib-loader/pom.xml @@ -5,36 +5,28 @@ minecraft-utils ru.progrm-jarvis.minecraft - 0.1.0-SNAPSHOT + 1.0.0-SNAPSHOT - minecraft-commons - - - org.spigotmc - spigot-api - - - net.md-5 - bungeecord-api - + lib-loader + + A lightweight module for automatic loading of libraries. + This is made to have no compile dependencies. + + org.projectlombok lombok - com.google.code.findbugs - jsr305 + org.jetbrains + annotations org.junit.jupiter junit-jupiter-api - - org.mockito - mockito-core - - \ No newline at end of file + diff --git a/lib-loader/src/main/java/ru/progrm_jarvis/minecraft/libloader/LibCoords.java b/lib-loader/src/main/java/ru/progrm_jarvis/minecraft/libloader/LibCoords.java new file mode 100644 index 000000000..f824bec75 --- /dev/null +++ b/lib-loader/src/main/java/ru/progrm_jarvis/minecraft/libloader/LibCoords.java @@ -0,0 +1,287 @@ +package ru.progrm_jarvis.minecraft.libloader; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import org.jetbrains.annotations.Nullable; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Optional; + +/** + * Coords of a library with dependencies. + */ +@FunctionalInterface +public interface LibCoords { + + String MAVEN_CENTRAL_REPO_URL = "https://repo1.maven.org/maven2/"; + + String SONATYPE_OSS_SNAPSHOTS_REPO_URL = "https://oss.sonatype.org/content/repositories/snapshots/"; + + /** + * Opens stream for accessing library artifact. + * + * @return URL of the artifact containing dependency classes + * @throws IOException if an IO-error occurs while opening stream + * + * @apiNote returned stream must be manually closed + * @apiNote should call{@link #assureIsRefreshed()} before all logic + */ + InputStream openArtifactStream() throws IOException; + + /** + * Computes the hashcode of the library not to repeat loading of the same library (if possible). + * + * @return optional containing hash of the artifact or empty optional if not available + * + * @apiNote this may be a time-consuming operation + * @apiNote should call{@link #assureIsRefreshed()} before all logic + */ + default Optional computeHash() { + return Optional.empty(); + } + + /** + * Refreshes the coordinate so that it is actualized. + */ + default void refresh() {} + + /** + * Checks whether this lib coords have been refreshed at least once. + * + * @return {@code true} if {@link #refresh()} have been called at least once or doesn't requre refreshing + * and {@code false} otherwise. + */ + default boolean isRefreshed() { + return true; + } + + /** + * Refreshes the lib coords if they haven't been yet. + */ + default void assureIsRefreshed() { + if (!isRefreshed()) refresh(); + } + + /** + * Gets the root URL for the repository of specified parameters. + * + * @param repositoryUrl URL of the repository of the artifact + * @param groupId artifact's {@code groupId} + * @param artifactId artifact's {@code artifactId} + * @param version artifact's version + * @return URl to the artifacts root ended with '/' + */ + static String getMavenArtifactsRootUrl(final @NonNull String repositoryUrl, + final @NonNull String groupId, final @NonNull String artifactId, + final @NonNull String version) { + return (repositoryUrl.lastIndexOf('/') == repositoryUrl.length() - 1 ? repositoryUrl : repositoryUrl + '/') + + groupId.replace('.', '/') + '/' + artifactId + '/' + version + '/'; + } + + @SneakyThrows(MalformedURLException.class) + static LibCoords fromMavenRepo(final @NonNull String repositoryUrl, + final @NonNull String groupId, final @NonNull String artifactId, + final @NonNull String version) { + val jarUrl = getMavenArtifactsRootUrl(repositoryUrl, groupId, artifactId, version) + + artifactId + '-' + version + ".jar"; + + return new MavenRepoLibCoords(new URL(jarUrl), new URL(jarUrl + ".sha1"), new URL(jarUrl + ".md5")); + } + + static LibCoords fromMavenCentralRepo(final @NonNull String groupId, final @NonNull String artifactId, + final @NonNull String version) { + return fromMavenRepo(MAVEN_CENTRAL_REPO_URL, groupId, artifactId, version); + } + + @SneakyThrows(MalformedURLException.class) + static LibCoords fromSonatypeNexusRepo(final @NonNull String repositoryUrl, + final @NonNull String groupId, final @NonNull String artifactId, + final @NonNull String version, final @NonNull String metadataFileName) { + val rootUrl = getMavenArtifactsRootUrl(repositoryUrl, groupId, artifactId, version); + + return new SonatypeNexusRepoLibCoords(new URL(rootUrl + metadataFileName), rootUrl); + } + + static LibCoords fromSonatypeNexusRepo(final @NonNull String repositoryUrl, + final @NonNull String groupId, final @NonNull String artifactId, + final @NonNull String version) { + return fromSonatypeNexusRepo( + repositoryUrl, groupId, artifactId, version, SonatypeNexusRepoLibCoords.METADATA_FILE_NAME + ); + } + + static LibCoords fromSonatypeOssSnapshotsRepo(final @NonNull String groupId, final @NonNull String artifactId, + final @NonNull String version) { + return fromSonatypeNexusRepo( + SONATYPE_OSS_SNAPSHOTS_REPO_URL, groupId, artifactId, + version, SonatypeNexusRepoLibCoords.METADATA_FILE_NAME + ); + } + + /** + * Gets the name of the artifact described in the specified {@code maven-metadata.xml} of Sonatype Nexus. + * + * @param documentElement {@code maven-metadata.xml} standard for Sonatype Nexus snapshots repository. + * @return name of the artifact specified in given {@code maven-metadata.xml} document element + * + * @see Document#getDocumentElement() to create {@link Element} from {@link Document} + */ + static String getLatestNexusArtifactName(final @NonNull Element documentElement) { + val version = documentElement.getElementsByTagName("version").item(0).getFirstChild().getTextContent(); + val snapshot = (Element) ((Element) documentElement.getElementsByTagName("versioning").item(0)) + .getElementsByTagName("snapshot").item(0); + + return documentElement.getElementsByTagName("artifactId").item(0).getFirstChild().getTextContent() + + '-' + (version.endsWith("-SNAPSHOT") ? version.substring(0, version.length() - 9) : version) + + '-' + snapshot.getElementsByTagName("timestamp").item(0).getFirstChild().getTextContent() + + '-' + snapshot.getElementsByTagName("buildNumber").item(0).getFirstChild().getTextContent(); + } + + /** + * Lib coords of an artifact normally contained in non-SNAPSHOT maven repository. + * Its artifact is accessed by direct URL. + * The hash is computed from their URL requests' contents concatenated using {@code '_'}. + */ + @Value + @FieldDefaults(level = AccessLevel.PRIVATE) + final class MavenRepoLibCoords implements LibCoords { + + /** + * URL of artifact + */ + @NonNull URL artifactUrl; + /** + * URL to be used (when self is non-null) to get the first hash of an artifact. + */ + @Nullable URL hash1Url, + /** + * URL to be used (when self and {@link #hash1Url} are non-null) to get the second hash of an artifact. + */ + hash2Url; + + public MavenRepoLibCoords(final @NonNull URL artifactUrl, + final @Nullable URL hash1Url, final @Nullable URL hash2Url) { + this.artifactUrl = artifactUrl; + this.hash1Url = hash1Url; + this.hash2Url = hash2Url; + } + + public MavenRepoLibCoords(final @NonNull URL jarUrl, final URL hash1Url) { + this(jarUrl, hash1Url, null); + } + + public MavenRepoLibCoords(final @NonNull URL jarUrl) { + this(jarUrl, null, null); + } + + @Override + public InputStream openArtifactStream() throws IOException { + assureIsRefreshed(); + + return artifactUrl.openStream(); + } + + @Override + public Optional computeHash() { + assureIsRefreshed(); + + if (hash1Url == null) return Optional.empty(); + try { + if (hash2Url == null) return Optional.ofNullable(LibLoader.readLineFromUrl(hash1Url)); + else { + val hash1 = LibLoader.readLineFromUrl(hash1Url); + if (hash1 == null) return Optional.empty(); + + val hash2 = LibLoader.readLineFromUrl(hash2Url); + if (hash2 == null) return Optional.empty(); + + return Optional.of(hash1 + '_' + hash2); + } + } catch (final IOException e) { + return Optional.empty(); + } + } + } + + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor + @FieldDefaults(level = AccessLevel.PRIVATE) + final class SonatypeNexusRepoLibCoords implements LibCoords { + + public static final String METADATA_FILE_NAME = "maven-metadata.xml"; + + /** + * Document builder factory singleton for internal usage. + */ + private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); + + /** + * URL of a metadata file to get the latest version of artifact. + */ + final @NonNull URL mavenMetadataUrl; + final @NonNull String artifactsRootUrl; + + /** + * Whether or not this lib coords were refreshed. + */ + @Getter boolean refreshed; + String latestVersion; + MavenRepoLibCoords mavenRepoLibCoords; + + @Override + @SneakyThrows({ParserConfigurationException.class, MalformedURLException.class}) + public void refresh() { + refreshed = true; + + final Document metadataDocument; + try { + val mavenMetadata = String.join(System.lineSeparator(), LibLoader.readLinesFromUrl(mavenMetadataUrl)); + if (mavenMetadata.isEmpty()) throw new RuntimeException( + "Could not read Maven metadata from " + mavenMetadataUrl + " as it's content is empty" + ); + metadataDocument = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder() + .parse(new InputSource(new StringReader(mavenMetadata))); + } catch (final IOException | SAXException e) { + throw new RuntimeException( + "Could not read Maven metadata from " + mavenMetadataUrl, e + ); + } + + val latestVersion = getLatestNexusArtifactName(metadataDocument.getDocumentElement()); + val jarUrl = artifactsRootUrl + latestVersion + ".jar"; + + if (!latestVersion.equals(this.latestVersion)) { + mavenRepoLibCoords = new MavenRepoLibCoords( + new URL(jarUrl), new URL(jarUrl + ".sha1"), new URL(jarUrl + ".md5") + ); + this.latestVersion = latestVersion; + } + } + + @Override + public InputStream openArtifactStream() throws IOException { + assureIsRefreshed(); + + return mavenRepoLibCoords.openArtifactStream(); + } + + @Override + public Optional computeHash() { + assureIsRefreshed(); + + return mavenRepoLibCoords.computeHash(); + } + } + +} diff --git a/lib-loader/src/main/java/ru/progrm_jarvis/minecraft/libloader/LibLoader.java b/lib-loader/src/main/java/ru/progrm_jarvis/minecraft/libloader/LibLoader.java new file mode 100644 index 000000000..28fedd1fc --- /dev/null +++ b/lib-loader/src/main/java/ru/progrm_jarvis/minecraft/libloader/LibLoader.java @@ -0,0 +1,332 @@ +package ru.progrm_jarvis.minecraft.libloader; + +import lombok.*; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +import lombok.extern.java.Log; + +import java.io.*; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A tiny loader of JAR dependencies to runtime. + */ +@Log +@ToString +@EqualsAndHashCode +@RequiredArgsConstructor +@Accessors(chain = true) +@FieldDefaults(level = AccessLevel.PRIVATE) +public class LibLoader { + + /** + * Method handle of {@code URLClassLoader.addURL(URL)} + */ + @NonNull private static final MethodHandle URL_CLASS_LOADER__ADD_URL_METHOD; + + /** + * Current class loader used by this lib loader + */ + @Getter @NonNull URLClassLoader classLoader; + + /** + * Directory to store library artifacts and hashes in + */ + @NonNull private final File rootDirectory; + + final @NonNull Map loadedLibs; + + // creates a MethodHandle object for URLClassLoader#addUrl(URL) method + static { + final Method addUrlMethod; + try { + addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + } catch (final NoSuchMethodException e) { + throw new ExceptionInInitializerError( + "Unable to initialize LibLoader as " + URLClassLoader.class.getCanonicalName() + + " is missing addURL(URL) method" + ); + } + + val accessible = addUrlMethod.isAccessible(); + addUrlMethod.setAccessible(true); + try { + URL_CLASS_LOADER__ADD_URL_METHOD = MethodHandles.lookup().unreflect(addUrlMethod); + } catch (final IllegalAccessException e) { + throw new ExceptionInInitializerError( + "Unable to initialize LibLoader as " + URLClassLoader.class.getCanonicalName() + + " cannot be unreflected to MethodHandle" + ); + } finally { + addUrlMethod.setAccessible(accessible); + } + } + + /** + * Tries to find an available {@link URLClassLoader} un current context. + * + * @return optional containing found {@link URLClassLoader} or empty if none was found in current context + * + * @implNote this checks current thread's class loader and system class loader + */ + public static Optional getAvailableUrlClassLoader() { + var classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader instanceof URLClassLoader) return Optional.of((URLClassLoader) classLoader); + + classLoader = ClassLoader.getSystemClassLoader(); + if (classLoader instanceof URLClassLoader) return Optional.of((URLClassLoader) classLoader); + + return Optional.empty(); + } + + /** + * Constructs new lib loader. This performs type-check of {@code classLoader} + * in order to guarantee that it is instance of {@link URLClassLoader}. + * + * @param classLoader class loader to use for loading of libraries, + * should normally be {@link URLClassLoader} + * @param rootDirectory directory to store library artifacts and hashes in + * + * @throws IllegalArgumentException if the {@code classLoader} is not {@link URLClassLoader} + */ + public LibLoader(final @NonNull ClassLoader classLoader, final @NonNull File rootDirectory) { + if (!(classLoader instanceof URLClassLoader)) throw new IllegalArgumentException( + classLoader + " is not instance of URLClassLoader" + ); + + loadedLibs = new ConcurrentHashMap<>(); + this.classLoader = (URLClassLoader) classLoader; + this.rootDirectory = rootDirectory; + } + + /** + * Constructs new lib loader using default root directory. + * + * @param urlClassLoader class loader to use for loading of libraries + */ + public LibLoader(final @NonNull URLClassLoader urlClassLoader) { + this(urlClassLoader, new File("libs/artifacts/")); + } + + /** + * Constructs new lib loader using default root directory. This performs type-check of {@code classLoader} + * * in order to guarantee that it is instance of {@link URLClassLoader}. + * + * @param classLoader class loader to use for loading of libraries, + * should normally be {@link URLClassLoader} + * + * @throws IllegalArgumentException if the {@code classLoader} is not {@link URLClassLoader} + */ + public LibLoader(final @NonNull ClassLoader classLoader) { + this(classLoader, new File("libs/artifacts/")); + } + + /** + * Constructs new lib loader. This uses class loader got from {@link #getAvailableUrlClassLoader()}. + * + * should normally be {@link URLClassLoader} + * @param rootDirectory directory to store library artifacts and hashes in + */ + public LibLoader(final @NonNull File rootDirectory) { + this(getAvailableUrlClassLoader().orElseThrow( + () -> new IllegalStateException("Cannot find any available URLClassLoader in current context") + ), rootDirectory + ); + } + + /** + * Sets this lib loader's class loader. + * + * @param classLoader class loader to be used by this lib loader + * @return self for chaining + * + * @see #setClassLoader(ClassLoader) variant performing checks of any classloader + */ + public LibLoader setClassLoader(final @NonNull URLClassLoader classLoader) { + this.classLoader = classLoader; + + return this; + } + + /** + * Sets this lib loader's class loader throwing an exception if it is not {@link URLClassLoader}. + * + * @param classLoader class loader to be used by this lib loader + * @throws IllegalArgumentException if the {@code classLoader} is not {@link URLClassLoader} + * @return self for chaining + * + * @see #setClassLoader(URLClassLoader) compile-time type-safe variant + */ + public LibLoader setClassLoader(final ClassLoader classLoader) { + if (!(classLoader instanceof URLClassLoader)) throw new IllegalArgumentException( + classLoader + " is not instance of URLClassLoader" + ); + + setClassLoader((URLClassLoader) classLoader); + + return this; + } + + /** + * Checks whether the lib by specified name was loaded or not. + * + * @param name name of the lib + * @return {@code true} if the lib was loaded by the specified name anf {@code false} otherwise + */ + public boolean isLibLoaded(final @NonNull String name) { + return loadedLibs.containsKey(name); + } + + /** + * Gets the loaded lib's artifact file if it was loaded. + * + * @param name name of the lib + * @return optional containing file og lib's artifact + * if it was loaded by the specified name or empty optional otherwise + */ + public Optional getLoadedLibArtifact(final @NonNull String name) { + return Optional.ofNullable(loadedLibs.get(name)); + } + + /** + * Call to this method guarantees that after it {@link #rootDirectory} will be a valid directory. + */ + @SneakyThrows + protected void assureRootDirectoryExists() { + if (!rootDirectory.isFile()) Files.createDirectories(rootDirectory.toPath()); + } + + /** + * Loads a library by its coords. + * Loading should happen if: + *

    + *
  • The library is missing
  • + *
  • The hash is not the same
  • + *
  • Hash is empty anywhere
  • + *
+ * + * @param name name to store this library by + * @param libCoords coords of a library artifact + * @param addToClasspath whether or not the library loaded should be added to classpath + * + * @return file of created artifact + */ + @SneakyThrows + public File loadLib(final @NonNull String name, final @NonNull LibCoords libCoords, final boolean addToClasspath) { + if (isLibLoaded(name)) throw new IllegalStateException("Library " + name + " is a;ready loaded by LibLoader"); + assureRootDirectoryExists(); + + log.info("Loading library " + name); + + val artifactFile = new File(rootDirectory, name + ".jar"); + val hashFile = new File(rootDirectory, name + ".hash"); + + libCoords.refresh(); + + val hash = libCoords.computeHash(); + + if (!artifactFile.isFile() || !hashFile.isFile() || !Files.lines(hashFile.toPath()).findFirst().equals(hash)) { + { + log.info("Downloading library " + name); + try (val stream = libCoords.openArtifactStream()) { + loadFromInputStream(stream, artifactFile); + } + } + Files.write(hashFile.toPath(), hash.orElse("").getBytes(StandardCharsets.UTF_8)); + } + + if (addToClasspath) addUrlToClasspath(classLoader, artifactFile.toURI().toURL()); + + return artifactFile; + } + + /** + * Loads a library by its coords adding it to classpath. + * Loading should happen if: + *
    + *
  • The library is missing
  • + *
  • The hash is not the same
  • + *
  • Hash is empty anywhere
  • + *
+ * + * @param name name to store this library by + * @param libCoords coords of a library artifact + * + * @return file of created artifact + * + * @see #loadLib(String, LibCoords, boolean) called with {@code addToClasspath} set to {@code true} + */ + @SneakyThrows + public File loadLib(final @NonNull String name, final @NonNull LibCoords libCoords) { + return loadLib(name, libCoords, true); + } + + /** + * Adds the specified URL to classpath of class loader. + * + * @param classLoader class loader to add the url to + * @param url url to add to classpath of class loader + */ + @SneakyThrows + public static void addUrlToClasspath(final @NonNull URLClassLoader classLoader, final @NonNull URL url) { + URL_CLASS_LOADER__ADD_URL_METHOD.invokeExact(classLoader, url); + } + + /////////////////////////////////////////////////////////////////////////// + // Network utility methods + /////////////////////////////////////////////////////////////////////////// + + /** + * Reads content of a URL as a list of lines. + * + * @param url url to read data from + * @return lines read from URL + * @throws IOException if an exception occurs while reading + */ + public static List readLinesFromUrl(final URL url) throws IOException { + try (val reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { + val lines = new ArrayList(); + + String line; + while ((line = reader.readLine()) != null) lines.add(line); + + return lines; + } + } + + /** + * Reads content of a URL as a single line. + * + * @param url url to read data from + * @return first line read from URL + * @throws IOException if an exception occurs while reading + */ + public static String readLineFromUrl(final URL url) throws IOException { + try (val reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { + return reader.readLine(); + } + } + + /** + * Loads a file from input stream to file specified. + * + * @param inputStream input stream from which to get the file + * @param file file to which to read read data + * @throws IOException if an exception occurs in an I/O operation + */ + public static void loadFromInputStream(final InputStream inputStream, final File file) throws IOException { + Files.copy(inputStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING); + } +} diff --git a/lib-loader/src/test/java/ru/progrm_jarvis/minecraft/libloader/LibCoordsTest.java b/lib-loader/src/test/java/ru/progrm_jarvis/minecraft/libloader/LibCoordsTest.java new file mode 100644 index 000000000..93b2b8ac7 --- /dev/null +++ b/lib-loader/src/test/java/ru/progrm_jarvis/minecraft/libloader/LibCoordsTest.java @@ -0,0 +1,46 @@ +package ru.progrm_jarvis.minecraft.libloader; + +import org.junit.jupiter.api.Test; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import java.io.File; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +class LibCoordsTest { + + @Test + void testGetLatestNexusArtifactName() + throws ParserConfigurationException, IOException, SAXException { + assertEquals( + "minecraft-commons-0.1.0-20190109.213531-6", + LibCoords.getLatestNexusArtifactName(DocumentBuilderFactory.newInstance() + .newDocumentBuilder().parse(new File(getClass().getResource("/maven-metadata.xml").getFile())) + .getDocumentElement() + ) + ); + } + + @Test + void testGetMavenArtifactsRootUrl() { + assertEquals( + "https://oss.sonatype.org/content/repositories/snapshots/" + + "ru/progrm-jarvis/minecraft/minecraft-commons/0.1.0-SNAPSHOT/", + LibCoords.getMavenArtifactsRootUrl( + "https://oss.sonatype.org/content/repositories/snapshots/", + "ru.progrm-jarvis.minecraft", "minecraft-commons", "0.1.0-SNAPSHOT") + ); + + assertEquals( + "https://oss.sonatype.org/content/repositories/snapshots/" + + "ru/progrm-jarvis/minecraft/minecraft-commons/0.1.0-SNAPSHOT/", + LibCoords.getMavenArtifactsRootUrl( + "https://oss.sonatype.org/content/repositories/snapshots", + "ru.progrm-jarvis.minecraft", "minecraft-commons", "0.1.0-SNAPSHOT") + ); + } +} \ No newline at end of file diff --git a/lib-loader/src/test/resources/maven-metadata.xml b/lib-loader/src/test/resources/maven-metadata.xml new file mode 100644 index 000000000..5fe2a0ad4 --- /dev/null +++ b/lib-loader/src/test/resources/maven-metadata.xml @@ -0,0 +1,59 @@ + + + ru.progrm-jarvis.minecraft + minecraft-commons + 0.1.0-SNAPSHOT + + + 20190109.213531 + 6 + + 20190109213531 + + + jar + 0.1.0-20190109.213531-6 + 20190109213531 + + + pom + 0.1.0-20190109.213531-6 + 20190109213531 + + + javadoc + jar + 0.1.0-20190109.213531-6 + 20190109213531 + + + sources + jar + 0.1.0-20190109.213531-6 + 20190109213531 + + + jar.asc + 0.1.0-20190109.213531-6 + 20190109213531 + + + pom.asc + 0.1.0-20190109.213531-6 + 20190109213531 + + + sources + jar.asc + 0.1.0-20190109.213531-6 + 20190109213531 + + + javadoc + jar.asc + 0.1.0-20190109.213531-6 + 20190109213531 + + + + diff --git a/mc-unit/pom.xml b/mc-unit/pom.xml index 29a745d5e..0fa4caac0 100644 --- a/mc-unit/pom.xml +++ b/mc-unit/pom.xml @@ -5,7 +5,7 @@ minecraft-utils ru.progrm-jarvis.minecraft - 0.1.0-SNAPSHOT + 1.0.0-SNAPSHOT mc-unit @@ -13,23 +13,18 @@ Utilities useful for JUnit testing minecraft-related functionality - - - - org.codehaus.mojo - aspectj-maven-plugin - - - - org.spigotmc spigot-api - net.sf.trove4j - trove4j + commons-io + commons-io + + + org.apache.httpcomponents + httpclient @@ -37,14 +32,19 @@ lombok - org.aspectj - aspectjrt + org.jetbrains + annotations com.google.code.findbugs jsr305 + + org.junit.jupiter + junit-jupiter-api + provided + org.mockito mockito-core @@ -56,4 +56,4 @@ provided - \ No newline at end of file + diff --git a/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/annotation/EnabledIfNms.java b/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/annotation/EnabledIfNms.java new file mode 100644 index 000000000..0c32873b7 --- /dev/null +++ b/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/annotation/EnabledIfNms.java @@ -0,0 +1,15 @@ +package ru.progrm_jarvis.mcunit.annotation; + +import org.junit.jupiter.api.extension.ExtendWith; +import ru.progrm_jarvis.mcunit.condition.NmsAvailableTestCondition; + +import java.lang.annotation.*; + +/** + * Indicates that the test should only be run if current environment has NMS-classes. + */ +@Documented +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(NmsAvailableTestCondition.class) +public @interface EnabledIfNms {} diff --git a/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/condition/NmsAvailableTestCondition.java b/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/condition/NmsAvailableTestCondition.java new file mode 100644 index 000000000..576b77691 --- /dev/null +++ b/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/condition/NmsAvailableTestCondition.java @@ -0,0 +1,27 @@ +package ru.progrm_jarvis.mcunit.condition; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import ru.progrm_jarvis.mcunit.annotation.EnabledIfNms; +import ru.progrm_jarvis.mcunit.util.NmsTestUtil; + +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; +import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; + +public final class NmsAvailableTestCondition implements ExecutionCondition { + + private static final @NotNull ConditionEvaluationResult + ENABLED_BY_DEFAULT = enabled('@' + EnabledIfNms.class.getName() + " is not present"), + ENABLED_WITH_NMS_AVAILABLE = enabled("Enabled with NMS available"), + DISABLED_WITH_NMS_UNAVAILABLE = disabled("Disabled with NMS unavailable"); + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(final @NotNull ExtensionContext context) { + return isAnnotated(context.getElement(), EnabledIfNms.class) + ? NmsTestUtil.isEnvironmentNms() ? ENABLED_WITH_NMS_AVAILABLE : DISABLED_WITH_NMS_UNAVAILABLE + : ENABLED_BY_DEFAULT; + } +} diff --git a/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/io/http/HttpClientArgumentMatchers.java b/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/io/http/HttpClientArgumentMatchers.java new file mode 100644 index 000000000..c8bac7719 --- /dev/null +++ b/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/io/http/HttpClientArgumentMatchers.java @@ -0,0 +1,63 @@ +package ru.progrm_jarvis.mcunit.io.http; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.Value; +import lombok.experimental.FieldDefaults; +import lombok.experimental.UtilityClass; +import org.apache.http.client.methods.HttpUriRequest; +import org.jetbrains.annotations.Nullable; +import org.mockito.ArgumentMatcher; +import org.mockito.ArgumentMatchers; + +import static org.mockito.ArgumentMatchers.argThat; + +/** + * Matchers for HTTP-client-related stuff. + * This is made mostly because commonly used classes of HTTP-client library + * don't override {@link Object#equals(Object)} and {@link Object#hashCode()} + */ +@UtilityClass +public class HttpClientArgumentMatchers { + + /** + * Creates a matcher for request specified. + * + * @param request request for which to create the matcher + * @return matcher for the request + * + * @implNote for {@code null} request {@link ArgumentMatchers#isNull()} is used. + */ + @NonNull public ArgumentMatcher httpUriRequestMatcher(final @Nullable HttpUriRequest request) { + return new HttpUriRequestMatcher(request); + } + + /** + * {@link HttpUriRequest} that is equal to the given value. + * + * @param value the given value + * @return {@code null} + */ + // naming and JavaDocs conventions taken from ArgumentMatchers + public HttpUriRequest eqHttpUriRequest(final @Nullable HttpUriRequest value) { + return argThat(httpUriRequestMatcher(value)); + } + + @Value + @FieldDefaults(level = AccessLevel.PRIVATE) + private static class HttpUriRequestMatcher implements ArgumentMatcher { + + @Nullable HttpUriRequest request; + + @Override + public boolean matches(final @Nullable HttpUriRequest argument) { + if (request == argument) return true; // if one non-null (as both not same) => match + if (request == null || argument == null) return false; + + // equality comparison + return request.isAborted() == argument.isAborted() // similar abort-status + && request.getMethod().equals(argument.getMethod()) // similar method type + && request.getURI().equals(argument.getURI()); // similar URI + } + } +} diff --git a/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/io/http/HttpClientMocks.java b/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/io/http/HttpClientMocks.java new file mode 100644 index 000000000..5911950ed --- /dev/null +++ b/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/io/http/HttpClientMocks.java @@ -0,0 +1,131 @@ +package ru.progrm_jarvis.mcunit.io.http; + +import lombok.NonNull; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import lombok.val; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpUriRequest; +import org.mockito.Answers; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Useful mocks for Apache HttpClient. + */ +@UtilityClass +public class HttpClientMocks { + + /** + * Creates a mock of {@link HttpClient} allowing easy behaviour mocking for request responses. + * + * @return mocked HTTP-client with support of response-mocking + */ + public MockedHttpClient mockHttpClient() { + return mock(MockedHttpClient.class, Answers.CALLS_REAL_METHODS); + } + + /** + * An HTTP-client mock base which allows easy addition of request-response behaviour mocks. + */ + public interface MockedHttpClient extends HttpClient { + + /** + * Makes this HTTP-client return specified response on specified request. + * + * @param request request on which to return the response specified + * @param response response to return on request specified + * @return this HTTP-client mock + */ + @SneakyThrows + default MockedHttpClient responding(final HttpUriRequest request, final HttpResponse response) { + when(execute(HttpClientArgumentMatchers.eqHttpUriRequest(request))).thenReturn(response); + + return this; + } + + /** + * Makes this HTTP-client return a mocked response + * whose {@link HttpResponse#getEntity()} method will return the one specified. + * + * @param request request on which to return the response mock with the specified entity + * @param responseEntity response entity to be used by response-mock returned on request specified + * @return this HTTP-client mock + */ + @SneakyThrows + default MockedHttpClient responding(final HttpUriRequest request, final HttpEntity responseEntity) { + val response = mock(HttpResponse.class); + when(response.getEntity()).thenReturn(responseEntity); + + return responding(request, response); + } + + /** + * Makes this HTTP-client return a mocked response + * whose {@link HttpResponse#getEntity()} method will return the mock + * whose {@link HttpEntity#getContent()} returns the one specified. + * + * @param request request on which to return the response mock with the specified entity + * @param responseContent response content of the entity-mock + * used by response-mock returned on request specified + * @return this HTTP-client mock + * + * @implNote does not influence any methods of {@link HttpEntity} except for {@link HttpEntity#getContent()} + */ + @SneakyThrows + default MockedHttpClient responding(final HttpUriRequest request, final InputStream responseContent) { + val responseEntity = mock(HttpEntity.class); + when(responseEntity.getContent()).thenReturn(responseContent); + + return responding(request, responseEntity); + } + + /** + * Makes this HTTP-client return a mocked response + * whose {@link HttpResponse#getEntity()} method will return the mock + * whose {@link HttpEntity#getContent()} returns the one based on the one specified. + * + * @param request request on which to return the response mock with the specified entity + * @param responseContent response content to be converted to {@link InputStream} of the entity-mock + * used by response-mock returned on request specified + * @param charset charset of the response content + * @return this HTTP-client mock + * + * @implNote does not influence any methods of {@link HttpEntity} except for {@link HttpEntity#getContent()} + * @implNote uses {@link IOUtils#toInputStream(CharSequence, Charset)} + * to convert the {@link String} to {@link InputStream} + */ + default MockedHttpClient responding(final HttpUriRequest request, final @NonNull String responseContent, + final Charset charset) { + return responding(request, IOUtils.toInputStream(responseContent, charset)); + } + + + /** + * Makes this HTTP-client return a mocked response + * whose {@link HttpResponse#getEntity()} method will return the mock + * whose {@link HttpEntity#getContent()} returns the one based on the one specified (treated as UTF-8 encoded). + * + * @param request request on which to return the response mock with the specified entity + * @param responseContent response content to be converted to {@link InputStream} + * of the entity-mock treated as UTF-8 encoded + * used by response-mock returned on request specified + * @return this HTTP-client mock + * + * @implNote does not influence any methods of {@link HttpEntity} except for {@link HttpEntity#getContent()} + * @implNote uses {@link IOUtils#toInputStream(CharSequence, Charset)} + * to convert the {@link String} to {@link InputStream} + */ + default MockedHttpClient responding(final HttpUriRequest request, final @NonNull String responseContent) { + return responding(request, responseContent, StandardCharsets.UTF_8); + } + } +} diff --git a/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/util/NmsTestUtil.java b/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/util/NmsTestUtil.java new file mode 100644 index 000000000..cd01730cb --- /dev/null +++ b/mc-unit/src/main/java/ru/progrm_jarvis/mcunit/util/NmsTestUtil.java @@ -0,0 +1,49 @@ +package ru.progrm_jarvis.mcunit.util; + +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.Server; + +import java.util.regex.Pattern; + +/** + * Utility for NMS-related functionality. + */ +@UtilityClass +public class NmsTestUtil { + + /** + * Pattern matching a server class name which has {@code v__R} in its package. + */ + private final Pattern NMS_SERVER_VERSION_PART_PATTERN = Pattern.compile("(?:\\w+\\.)*v\\d+_\\d+_R\\d+\\.\\w+"); + + /** + * Checks whether the specified class name is a valid NMS server name. + * + * @param serverName server name to check + * @return {@code true} if the class name is a valid NM-server name and {@code false} otherwise + * + * @apiNote valid NMS-server name contains {@code v__R} in its package + */ + public boolean isNmsServerClassName(final @NonNull String serverName) { + return NMS_SERVER_VERSION_PART_PATTERN.matcher(serverName).matches(); + } + + /** + * Checks whether current environment is an NMS-environment. + * + * @return {@code true} if current environment is (possibly) a valid NMS-environment + * and {@code false} otherwise + * + * @apiNote this method checks whether {@link Bukkit#getServer()} is not {@code null} + * and that its canonical class name is a valid NMS-server class name + * + * @see #isNmsServerClassName(String) method used for checking {@link Bukkit#getServer()}'s class's canonical name + */ + public boolean isEnvironmentNms() { + final Server server; + //noinspection ConstantConditions: `getSetver()` actually nullable + return (server = Bukkit.getServer()) != null && isNmsServerClassName(server.getClass().getCanonicalName()); + } +} diff --git a/mc-unit/src/test/java/ru/progrm_jarvis/mcunit/io/http/HttpClientArgumentMatchersTest.java b/mc-unit/src/test/java/ru/progrm_jarvis/mcunit/io/http/HttpClientArgumentMatchersTest.java new file mode 100644 index 000000000..443896a2a --- /dev/null +++ b/mc-unit/src/test/java/ru/progrm_jarvis/mcunit/io/http/HttpClientArgumentMatchersTest.java @@ -0,0 +1,33 @@ +package ru.progrm_jarvis.mcunit.io.http; + +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class HttpClientArgumentMatchersTest { + + @Test + void testHttpUriRequestMatcher() { + assertTrue(HttpClientArgumentMatchers.httpUriRequestMatcher(new HttpGet("https://github.com/")) + .matches(new HttpGet("https://github.com/"))); + + assertTrue(HttpClientArgumentMatchers.httpUriRequestMatcher(new HttpPost("https://github.com/")) + .matches(new HttpPost("https://github.com/"))); + + assertFalse(HttpClientArgumentMatchers.httpUriRequestMatcher(new HttpGet("https://github.com/")) + .matches(new HttpPost("https://github.com/"))); + + assertFalse(HttpClientArgumentMatchers.httpUriRequestMatcher(new HttpGet("https://example.com/")) + .matches(new HttpGet("https://github.com/"))); + + assertFalse(HttpClientArgumentMatchers.httpUriRequestMatcher(new HttpPost("https://example.com/")) + .matches(new HttpPost("https://github.com/"))); + + assertTrue(HttpClientArgumentMatchers.httpUriRequestMatcher(null).matches(null)); + + assertFalse(HttpClientArgumentMatchers.httpUriRequestMatcher(new HttpPost("https://example.com/")) + .matches(null)); + } +} \ No newline at end of file diff --git a/mc-unit/src/test/java/ru/progrm_jarvis/mcunit/io/http/HttpClientMocksTest.java b/mc-unit/src/test/java/ru/progrm_jarvis/mcunit/io/http/HttpClientMocksTest.java new file mode 100644 index 000000000..b006e6dbd --- /dev/null +++ b/mc-unit/src/test/java/ru/progrm_jarvis/mcunit/io/http/HttpClientMocksTest.java @@ -0,0 +1,39 @@ +package ru.progrm_jarvis.mcunit.io.http; + +import lombok.val; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +class HttpClientMocksTest { + + @Test + void testMockedHttpClient() throws IOException { + val someResponse = mock(HttpResponse.class); + + val client = HttpClientMocks.mockHttpClient() + .responding(new HttpGet("http://example.com/"), "Hello world") + .responding(new HttpPost("http://example.com/"), "Pochta Rossii != Post") + .responding(new HttpGet("http://example2.com/"), someResponse); + + assertEquals("Hello world", IOUtils.toString( + client.execute(new HttpGet("http://example.com/")).getEntity().getContent(), StandardCharsets.UTF_8 + )); + + assertEquals("Pochta Rossii != Post", IOUtils.toString( + client.execute(new HttpPost("http://example.com/")).getEntity().getContent(), StandardCharsets.UTF_8 + )); + + assertSame(someResponse, client.execute(new HttpGet("http://example2.com/"))); + + assertNull(client.execute(new HttpGet("http://not-example.com/"))); + } +} \ No newline at end of file diff --git a/minecraft-commons/pom.xml b/minecraft-commons/pom.xml new file mode 100644 index 000000000..3c3084cd1 --- /dev/null +++ b/minecraft-commons/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + minecraft-utils + ru.progrm-jarvis.minecraft + 1.0.0-SNAPSHOT + + minecraft-commons + + + + ru.progrm-jarvis.minecraft + mc-unit + + + + + org.hamcrest + hamcrest-all + + + + ru.progrm-jarvis + java-commons + + + ru.progrm-jarvis + reflector + + + ru.progrm-jarvis.minecraft + packet-wrapper + + + org.spigotmc + spigot-api + + + net.md-5 + bungeecord-api + + + org.apache.httpcomponents + httpclient + + + com.comphenix.protocol + ProtocolLib + + + com.mojang + authlib + + + + org.projectlombok + lombok + + + com.google.code.findbugs + jsr305 + + + org.jetbrains + annotations + + + + org.junit.jupiter + junit-jupiter-api + + + org.junit.jupiter + junit-jupiter-params + + + org.mockito + mockito-core + + + org.mockito + mockito-junit-jupiter + + + it.unimi.dsi + fastutil + + + diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/MinecraftCommons.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/MinecraftCommons.java new file mode 100644 index 000000000..5d46c750d --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/MinecraftCommons.java @@ -0,0 +1,60 @@ +package ru.progrm_jarvis.minecraft.commons; + +import lombok.experimental.UtilityClass; +import ru.progrm_jarvis.minecraft.commons.util.SystemPropertyUtil; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +/** + * Utility for accessing general configurations of Minecraft Commons. + */ +@UtilityClass +public class MinecraftCommons { + + /** + * Root folder of minecraft-commons shared files + */ + public final File ROOT_DIRECTORY = new File(SystemPropertyUtil.getSystemProperty( + MinecraftCommons.class.getCanonicalName() + ".root-directory", Function.identity(), + "plugins/minecraft_commons/" + )); + + private final boolean CREATE_README_FILE = SystemPropertyUtil.getSystemPropertyBoolean( + MinecraftCommons.class.getCanonicalName() + ".create-readme-file", true + ); + + /** + * Content of {@code README.txt} file created in {@link #ROOT_DIRECTORY} if this option is not disabled. + */ + public final List README_CONTENT = Arrays.asList( + "This is an internal folder of minecraft-commons library.", + "It is most likely used by one or more of your plugins and should not be removed " + + "as it may store some sensitive data", + "", + "minecraft-commons is part of minecraft-utils open-source project " + + "and is distributed under Apache 2.0 license", + "Source code is available at: https://github.com/JarvisCraft/minecraft-utils", + "", + "minecraft-commons and minecraft-utils development is not related to Mojang AB, Microsoft or Minecraft", + "", + " ~ PROgrm_JARvis#"); + + static { + if (!ROOT_DIRECTORY.isFile()) try { + Files.createDirectories(ROOT_DIRECTORY.toPath()); + } catch (final IOException e) { + throw new RuntimeException("Unable to create minecraft-commons root directory", e); + } + // create readme file if its generation is not disabled + if (CREATE_README_FILE) try { + Files.write(new File(ROOT_DIRECTORY, "README.txt").toPath(), README_CONTENT); + } catch (final IOException e) { + throw new RuntimeException("Unable to create README.txt for minecraft-commons", e); + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/MinecraftEnvironment.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/MinecraftEnvironment.java new file mode 100644 index 000000000..86873e687 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/MinecraftEnvironment.java @@ -0,0 +1,60 @@ +package ru.progrm_jarvis.minecraft.commons; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.Synchronized; +import lombok.experimental.FieldDefaults; +import lombok.experimental.NonFinal; +import lombok.val; +import ru.progrm_jarvis.minecraft.commons.util.ReflectionUtil; + +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public enum MinecraftEnvironment { + + BUKKIT_API("org.bukkit.Bukkit"), + SPIGOT_API("org.spigotmc.CustomTimingsHandler"), + NMS(new String[0], "net.minecraft.server"), + CRAFTBUKKIT(new String[0], "org.bukkit.craftbukkit"), + BUNGEE_API("net.md_5.bungee.api.ProxyServer"), + BUNGEECORD("net.md_5.bungee.BungeeCord"); + + @NonFinal boolean available; + @NonNull String[] checkedClasses, checkedPackages; + + MinecraftEnvironment(final @NonNull String[] checkedClasses, final @NonNull String... checkedPackages) { + this.checkedClasses = checkedClasses; + this.checkedPackages = checkedPackages; + } + + MinecraftEnvironment(final String... checkedClasses) { + this(checkedClasses, new String[0]); + } + + /** + * Forcefully sets this environment as an available. + * + * @deprecated Should only be called in case the specified environment is available although cannot be recognized. + */ + @Deprecated + @Synchronized + public void setAvailable() { + available = true; + } + + @Synchronized + public boolean isAvailable() { + return available || (available = classesAvailable() && packagesAvailable()); + } + + private boolean packagesAvailable() { + if (checkedPackages.length == 0) return true; + for (val checkedPackage : checkedPackages) if (Package.getPackage(checkedPackage) == null) return false; + return true; + } + + private boolean classesAvailable() { + if (checkedClasses.length == 0) return true; + for (val checkedClass : checkedClasses) if (!ReflectionUtil.isClassAvailable(checkedClass)) return false; + return true; + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/annotation/AsyncExpected.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/annotation/AsyncExpected.java new file mode 100644 index 000000000..9be34753e --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/annotation/AsyncExpected.java @@ -0,0 +1,20 @@ +package ru.progrm_jarvis.minecraft.commons.annotation; + +import java.lang.annotation.*; + +/** + * A marker indicating that the method annotated should be used asynchronously. + * This commonly means that this method may perform time-consuming operations. + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface AsyncExpected { + + /** + * Indicates which method to use instead as an alternative allowing synchronous calls. + * + * @return synchronous alternative + */ + String value() default ""; +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/annotation/BukkitService.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/annotation/BukkitService.java new file mode 100644 index 000000000..e74324249 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/annotation/BukkitService.java @@ -0,0 +1,14 @@ +package ru.progrm_jarvis.minecraft.commons.annotation; + +import java.lang.annotation.*; + +/** + * Marker to indicate that the annotated element is normally registered as a Bukkit service. + * + * @see org.bukkit.plugin.ServicesManager#getRegistration(Class) + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface BukkitService { +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/annotation/UnsafeNMS.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/annotation/UnsafeNMS.java new file mode 100644 index 000000000..422d58780 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/annotation/UnsafeNMS.java @@ -0,0 +1,13 @@ +package ru.progrm_jarvis.minecraft.commons.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Marker to indicate that the annotated element may use unsafe {@code net.minecraft.server} functionality. + */ +@Documented +@Retention(RetentionPolicy.CLASS) +public @interface UnsafeNMS { +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/async/AsyncRunner.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/async/AsyncRunner.java new file mode 100644 index 000000000..7b0200671 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/async/AsyncRunner.java @@ -0,0 +1,29 @@ +package ru.progrm_jarvis.minecraft.commons.async; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * An object capable of performing asynchronous operations. + */ +@FunctionalInterface +public interface AsyncRunner { + + /** + * Performs the specified operations asynchronously. + * + * @param operation operation to perform asynchronously + */ + void runAsynchronously(final Runnable operation); + + /** + * Performs the specified operations asynchronously. + * + * @param operation operation to perform asynchronously + * @param callback callback to handle the resulting value of the operation + * @param type of value returned by the operation + */ + default void runAsynchronously(final Supplier operation, final Consumer callback) { + runAsynchronously(() -> callback.accept(operation.get())); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/async/AsyncRunners.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/async/AsyncRunners.java new file mode 100644 index 000000000..c080a26eb --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/async/AsyncRunners.java @@ -0,0 +1,42 @@ +package ru.progrm_jarvis.minecraft.commons.async; + +import lombok.experimental.UtilityClass; +import org.jetbrains.annotations.Nullable; +import ru.progrm_jarvis.minecraft.commons.MinecraftEnvironment; + +/** + * Utilities for easier use of {@link AsyncRunner}s. + */ +@UtilityClass +public class AsyncRunners { + + /** + * Gets an {@link AsyncRunner} aware of current {@link MinecraftEnvironment}. + * + * @param bukkitPlugin Bukkit plugin if there is one to be used for Bukkit async caller + * @param bungeePlugin BungeeCord plugin if there is one to be used for BungeeCord async caller + * @return an async caller capable of performing its operations in current environment + * @throws IllegalStateException if there is no async caller available for current context + * + * @implNote This will give a caller of Bukkit or Bungee depending on availability of their classes + */ + public AsyncRunner getMinecraftEnvironmentAware(final @Nullable Object bukkitPlugin, + final @Nullable Object bungeePlugin) { + // try use Bukkit's + if (MinecraftEnvironment.BUKKIT_API.isAvailable()) attempt: { + if (bukkitPlugin == null) break attempt; + + return new BukkitSchedulerAsyncRunner((org.bukkit.plugin.Plugin) bukkitPlugin); + } + + // try use BungeeCord's + if (MinecraftEnvironment.BUNGEE_API.isAvailable()) attempt: { + if (bungeePlugin == null) break attempt; + + return new BungeeSchedulerAsyncRunner((net.md_5.bungee.api.plugin.Plugin) bungeePlugin); + } + + throw new IllegalStateException("No AsyncRunner found for current Minecraft environment " + + "and specified plugins:" + bukkitPlugin + " [Bukkit], " + bungeePlugin + " [BungeeCord]"); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/async/BukkitSchedulerAsyncRunner.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/async/BukkitSchedulerAsyncRunner.java new file mode 100644 index 000000000..91faa9ea5 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/async/BukkitSchedulerAsyncRunner.java @@ -0,0 +1,25 @@ +package ru.progrm_jarvis.minecraft.commons.async; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import org.bukkit.plugin.Plugin; + +/** + * Async runner based on {@link org.bukkit.scheduler.BukkitScheduler}. + */ +@ToString +@EqualsAndHashCode +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class BukkitSchedulerAsyncRunner implements AsyncRunner { + + /** + * Plugin to be used for scheduling asynchronous operations. + */ + @NonNull Plugin plugin; + + @Override + public void runAsynchronously(final Runnable operation) { + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, operation); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/async/BungeeSchedulerAsyncRunner.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/async/BungeeSchedulerAsyncRunner.java new file mode 100644 index 000000000..eb8846c7d --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/async/BungeeSchedulerAsyncRunner.java @@ -0,0 +1,26 @@ +package ru.progrm_jarvis.minecraft.commons.async; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.plugin.Plugin; + +/** + * Async runner based on {@link net.md_5.bungee.api.scheduler.TaskScheduler}. + */ +@ToString +@EqualsAndHashCode +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class BungeeSchedulerAsyncRunner implements AsyncRunner { + + /** + * Plugin to be used for scheduling asynchronous operations. + */ + @NonNull Plugin plugin; + + @Override + public void runAsynchronously(final Runnable operation) { + ProxyServer.getInstance().getScheduler().runAsync(plugin, operation); + } +} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/block/BlocksChain.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/block/BlocksChain.java similarity index 90% rename from commons/src/main/java/ru/progrm_jarvis/minecraft/commons/block/BlocksChain.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/block/BlocksChain.java index 7b1acfeaf..825202593 100644 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/block/BlocksChain.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/block/BlocksChain.java @@ -12,7 +12,7 @@ * A chain of operations of {@link org.bukkit.block.Block}s in some {@link org.bukkit.World}. * The chain of operations */ -public interface BlocksChain

extends BukkitPluginContainer

, Iterator { +public interface BlocksChain extends BukkitPluginContainer, Iterator { /** * Gets the world in which this blocks chain exists. diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/block/SnakyBlockChain.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/block/SnakyBlockChain.java similarity index 76% rename from commons/src/main/java/ru/progrm_jarvis/minecraft/commons/block/SnakyBlockChain.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/block/SnakyBlockChain.java index 6ae22db5f..4c327414a 100644 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/block/SnakyBlockChain.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/block/SnakyBlockChain.java @@ -11,11 +11,11 @@ @ToString @EqualsAndHashCode @FieldDefaults(level = AccessLevel.PROTECTED) -public abstract class SnakyBlockChain

implements BlocksChain

{ +public abstract class SnakyBlockChain implements BlocksChain { - @NonNull final P plugin; - @NonNull final @Getter World world; - @NonNull final @Getter Block initialBlock; + final @NonNull Plugin plugin; + @Getter final @NonNull World world; + @Getter final @NonNull Block initialBlock; /** * The current layer of blocks to handle @@ -30,13 +30,13 @@ public abstract class SnakyBlockChain

implements BlocksChain

handledBlocks; + final @NonNull Set handledBlocks; - protected SnakyBlockChain(@NonNull final P plugin, final @NonNull Block initialBlock, - @NonNull final Queue blocks, final @NonNull Queue nextBlocks, - @NonNull final Set handledBlocks) { + protected SnakyBlockChain(final @NonNull Plugin plugin, final @NonNull Block initialBlock, + final @NonNull Queue blocks, final @NonNull Queue nextBlocks, + final @NonNull Set handledBlocks) { this.plugin = plugin; - this.world = initialBlock.getWorld(); + world = initialBlock.getWorld(); this.initialBlock = initialBlock; this.blocks = blocks; this.nextBlocks = nextBlocks; @@ -45,14 +45,14 @@ protected SnakyBlockChain(@NonNull final P plugin, final @NonNull Block initialB blocks.add(initialBlock); } - protected SnakyBlockChain(@NonNull final P plugin, @NonNull final Block initialBlock) { + protected SnakyBlockChain(final @NonNull Plugin plugin, final @NonNull Block initialBlock) { this(plugin, initialBlock, new ArrayDeque<>(), new ArrayDeque<>(), new HashSet<>()); } - public static

SnakyBlockChain

create(@NonNull final P plugin, - @NonNull final Block initialBlock, - @NonNull final BlockHandler blockHandler) { - return new SnakyBlockChain

(plugin, initialBlock) { + public static SnakyBlockChain create(final @NonNull Plugin plugin, + final @NonNull Block initialBlock, + final @NonNull BlockHandler blockHandler) { + return new SnakyBlockChain(plugin, initialBlock) { @Override protected Collection handle(final Block block) { return blockHandler.handle(block); @@ -107,7 +107,7 @@ public Block next() { * @return bukkit plugin of this object */ @Override - public P getBukkitPlugin() { + public Plugin getBukkitPlugin() { return plugin; } diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkLocalLocation.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkLocalLocation.java new file mode 100644 index 000000000..226fe6e1d --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkLocalLocation.java @@ -0,0 +1,24 @@ +package ru.progrm_jarvis.minecraft.commons.chunk; + +import lombok.AccessLevel; +import lombok.Value; +import lombok.experimental.FieldDefaults; +import org.bukkit.Chunk; + +/** + * Immutable value storing chunk and its location + */ +@Value +@FieldDefaults(level = AccessLevel.PRIVATE) +public final class ChunkLocalLocation { + + /** + * Chunk containing the location + */ + Chunk chunk; + + /** + * Chunk-local location in a {@code short} representation + */ + short location; +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkUtil.java new file mode 100644 index 000000000..7178277ea --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkUtil.java @@ -0,0 +1,272 @@ +package ru.progrm_jarvis.minecraft.commons.chunk; + +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Utilities related to chunks. + *

+ * There are general conventions for type=methods: + * + *

+ *
{@code Foo [bar]foo(Bar...)}
+ *
return the specified bar value's foocomponent (other data is lost)
+ * + *
{@code Foo [bar]toFoo(Bar...)}
+ *
convert the specified bar value to its foo representation (no data is lost)
+ * + *
{@code Foo [Bar]Foo(Bar...)}
+ *
performs the action specified on bar using foo
+ *
+ * + * @apiNote chunks are (by default) returned as a single {@code long} as the limit of chunk at non-Y-axis is 3750000 + * where 32 most significant bits stand for X-coordinate and 32 least significant bits stand for Z-coordinate + * @apiNote chunks-local locations are (by default) returned as a single {@code short} + * whose bits are ordered as 4 bits for X-coordinate, 8 bits for Y-coordinate, 4 bits for Z-coordinate + * @see General info about chinks + */ +@UtilityClass +public class ChunkUtil { + + /////////////////////////////////////////////////////////////////////////// + // Range checks + /////////////////////////////////////////////////////////////////////////// + + /** + * Performs a range-check of chunk-local coordinates. + * + * @param x chunk-local X-coordinate which should normally be between 0 and 15 + * @param y chunk-local Y-coordinate which should normally be between 0 and 255 + * @param z chunk-local Z-coordinate which should normally be between 0 and 15 + * @throws IllegalArgumentException if any of coordinates is out of allowed range + */ + public void rangeCheckChunkLocal(final int x, final int y, final int z) { + rangeCheckChunkLocalX(x); + rangeCheckChunkLocalY(y); + rangeCheckChunkLocalX(z); + } + + /** + * Performs a range-check of chunk-local X-coordinate. + * + * @param x chunk-local X-coordinate which should normally be between 0 and 15 + * @throws IllegalArgumentException if {@code x} is not in range between 0 and 15 + */ + public void rangeCheckChunkLocalX(final int x) { + checkArgument(x >= 0 && x <= 15, "x should be between 0 and 15"); + } + + /** + * Performs a range-check of chunk-local Y-coordinate. + * + * @param y chunk-local Y-coordinate which should normally be between 0 and 255 + * @throws IllegalArgumentException if {@code y} is not in range between 0 and 255 + */ + public void rangeCheckChunkLocalY(final int y) { + checkArgument(y >= 0 && y <= 255, "z should be between 0 and 255"); + } + + /** + * Performs a range-check of chunk-local Z-coordinate. + * + * @param z chunk-local Z-coordinate which should normally be between 0 and 15 + * @throws IllegalArgumentException if {@code z} is not in range between 0 and 15 + */ + public void rangeCheckChunkLocalZ(final int z) { + checkArgument(z >= 0 && z <= 15, "z should be between 0 and 15"); + } + + /////////////////////////////////////////////////////////////////////////// + // Chunk + /////////////////////////////////////////////////////////////////////////// + + /** + * Returns a single {@code long} value storing chunk data for X- and Z-axises. + * + * @param x X coordinate of a chunk + * @param z Z coordinate of a chunk + * @return chunk treated as {@code long} + */ + public long toChunkLong(final int x, final int z) { + return ((long) x << 32) | ((long) z & 0xFFFFFFFFL); + } + + /** + * Returns the X coordinate value from a long-serialized chunk + * + * @param longChunk chunk data treated as {@code long} + * @return X coordinate of a chunk + */ + public int chunkX(final long longChunk) { + return (int) (longChunk >> 32); + } + + /** + * Returns the Z coordinate value from a long-serialized chunk + * + * @param longChunk chunk data treated as long + * @return Z coordinate of a chunk + */ + public int chunkZ(final long longChunk) { + return (int) (longChunk); + } + + /** + * Gets the chunk by location in a world. + * + * @param x X coordinate of the location + * @param z Y coordinate of the location + * @return chunk location treated as {@code long} + */ + public long chunkAt(final long x, final long z) { + return toChunkLong((int) (x >> 4), (int) (z >> 4)); + } + + /** + * Gets the chunk in the world specified from a {@code long} chunk representation. + * + * @param world world to get chunk from + * @param chunk chunk treated as {@code long} + * @return specified chunk of the world + */ + public Chunk getChunk(final @NonNull World world, final long chunk) { + return world.getChunkAt(chunkX(chunk), chunkZ(chunk)); + } + + /////////////////////////////////////////////////////////////////////////// + // Chunk local location + /////////////////////////////////////////////////////////////////////////// + + /** + * Converts the specified x, y and z chunk-local coordinates to a {@code short}-representation + * + * @param chunkLocalX X-coordinate inside the chunk between 0 and 15 + * @param chunkLocalY Y-coordinate inside the chunk between 0 and 255 + * @param chunkLocalZ Z-coordinate inside the chunk between 0 and 15 + * @return {@code short}-representation of specified chunk-local coordinated + * + * @throws IllegalArgumentException if {@code x} is not in range [0; 15] + * @throws IllegalArgumentException if {@code z} is not in range [0; 15] + * @throws IllegalArgumentException if {@code y} is not in range [0; 255] + */ + public short toChunkLocalLocationShort(final int chunkLocalX, final int chunkLocalY, final int chunkLocalZ) { + rangeCheckChunkLocal(chunkLocalX, chunkLocalY, chunkLocalZ); + + return (short) (((chunkLocalX & 0xF) << 12) | ((chunkLocalY & 0xFF) << 4) | (chunkLocalZ & 0xF)); + } + + /** + * Gets the X-coordinate from a {@code short}-representation of a chunk-local location. + * + * @param location {@code short}-representation of a chunk-local location + * @return X-coordinate of a chunk local location + */ + public int chunkLocalLocationX(final short location) { + return (location >> 12) & 0xF; + } + + /** + * Gets the Y-coordinate from a {@code short}-representation of a chunk-local location. + * + * @param location {@code short}-representation of a chunk-local location + * @return Y-coordinate of a chunk local location + */ + public int chunkLocalLocationY(final short location) { + return (location >> 4) & 0xFF; + } + + /** + * Gets the Z-coordinate from a {@code short}-representation of a chunk-local location. + * + * @param location {@code short}-representation of a chunk-local location + * @return Z-coordinate of a chunk local location + */ + public int chunkLocalLocationZ(final short location) { + return location & 0xF; + } + + /** + * Gets the chunk-local X-coordinate value of the location. + * + * @param x X-coordinate + * @return chunk-local X-coordinate + */ + public int chunkLocalX(final int x) { + return x & 0xF; + } + + /** + * Gets the chunk-local Y-coordinate value of the location. + * + * @param y Y-coordinate + * @return chunk-local Y-coordinate + */ + public int chunkLocalY(final int y) { + return y & 0xFF; + } + + /** + * Gets the chunk-local Z-coordinate value of the location. + * + * @param z Z-coordinate + * @return chunk-local Z-coordinate + */ + public int chunkLocalZ(final int z) { + return z & 0xF; + } + + /** + * Gets the specified location's chunk-local location. + * + * @param x X-coordinate of the location whose chunk-local location should be got + * @param y Y-coordinate of the location whose chunk-local location should be got + * @param z Z-coordinate of the location whose chunk-local location should be got + * @return {@code short}-representation of location's chunk-local location + * + * @see #chunkLocalLocation(Location) is an allias for {@link Location} argument + */ + public short chunkLocalLocation(final int x, final int y, final int z) { + return toChunkLocalLocationShort(chunkLocalX(x), chunkLocalY(y), chunkLocalZ(z)); + } + + /** + * Gets the specified location's chunk-local location. + * + * @param location location whose chunk-local location should be got + * @return {@code short}-representation of location's chunk-local location + * + * @see #toChunkLocalLocationShort(int, int, int) is called with location's coordinates + */ + public short chunkLocalLocation(final @NonNull Location location) { + return chunkLocalLocation(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + } + + /** + * Gets the block in a chunk from its chunk-local location in {@code short}-representation + * + * @param chunk chunk whose block to get + * @param location {@code short}-representation of a chunk-local location + * @return block from the chunk of specified chunk-local location + */ + public Block getChunkBlock(final Chunk chunk, final short location) { + return chunk.getBlock( + chunkLocalLocationX(location), chunkLocalLocationY(location), chunkLocalLocationZ(location) + ); + } + + /** + * Converts location to it's chunk-local location representation. + * + * @param location location whose chunk-local location should be got + * @return chunk local location of the location + */ + public ChunkLocalLocation toChunkLocalLocation(final Location location) { + return new ChunkLocalLocation(location.getChunk(), chunkLocalLocation(location)); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/enchant/Enchant.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/enchant/Enchant.java new file mode 100644 index 000000000..74dd1eed1 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/enchant/Enchant.java @@ -0,0 +1,26 @@ +package ru.progrm_jarvis.minecraft.commons.enchant; + +import com.google.common.base.Preconditions; +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.Value; +import lombok.experimental.FieldDefaults; +import org.bukkit.enchantments.Enchantment; + +import javax.annotation.Nonnegative; + +@Value(staticConstructor = "of") +@FieldDefaults(level = AccessLevel.PRIVATE) +public class Enchant { + + @NonNull Enchantment enchantment; + @Nonnegative int level; + + + public Enchant(final @NonNull Enchantment enchantment, final int level) { + Preconditions.checkArgument(level >= 0, "Level should be non-negative"); + + this.enchantment = enchantment; + this.level = level; + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/event/FluentBukkitEvents.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/event/FluentBukkitEvents.java new file mode 100644 index 000000000..246aad856 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/event/FluentBukkitEvents.java @@ -0,0 +1,174 @@ +package ru.progrm_jarvis.minecraft.commons.event; + +import lombok.*; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +import lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; +import org.jetbrains.annotations.NotNull; +import ru.progrm_jarvis.minecraft.commons.util.shutdown.Shutdownable; + +import java.util.Deque; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.function.Consumer; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Utility for fluent and comfortable registration of events. + */ +@UtilityClass +public class FluentBukkitEvents { + + /** + * Plugin manager of Bukkit + */ + private final PluginManager PLUGIN_MANAGER = Bukkit.getPluginManager(); + + /** + * Event listeners groups stored by Plugin -> Event-Type -> EventPriority. + */ + private final Map, EventListenersGroup> LISTENERS_GROUPS = new ConcurrentHashMap<>(); + + /** + * Creates an event listener registration for fluent registration of the event listener(s). + * + * @param eventType type of the event handled to use for registration + * @param type of the event handled + * @return event listener registration for fluent registration of the event listener(s) + */ + public EventListenerRegistration on(final @NonNull Class eventType) { + return new EventListenerRegistration<>(eventType); + } + + @Data + @Accessors(chain = true, fluent = true) + public static final class EventListenerRegistration { + + /** + * Type of event registered + */ + @NonNull Class type; + + /** + * Plugin to use for event registration + */ + @NonNull Plugin plugin; + + /** + * Priority of the event's handler + */ + @NonNull EventPriority priority = EventPriority.NORMAL; + + /** + * Instantiates new event listener registration for the specified event type. + * + * @param type type of event handled + */ + public EventListenerRegistration(final @NonNull Class type) { + this.type = type; + } + + /** + * Gets the event listeners group for this event listeners registration's configuration. + * + * @return event listeners group for this event listener registration's configuration + */ + @SuppressWarnings("unchecked") + private EventListenersGroup getListenersGroup() { + checkNotNull(plugin, "plugin has not been set"); + + return (EventListenersGroup) LISTENERS_GROUPS.computeIfAbsent( + new ListenerConfiguration<>(plugin, type, priority), + configuration -> new EventListenersGroup<>((ListenerConfiguration) configuration) + ); + } + + /** + * Registers the event. + * + * @param listener listener to use for event handling + * @return unregister to use for event unregistration + */ + public Shutdownable register(final @NonNull Consumer listener) { + val listenersGroup = getListenersGroup(); + listenersGroup.addListener(listener); + + return () -> listenersGroup.removeListener(listener); + } + } + + /** + * Group of event listeners having the same parameters. + * + * @param type of handled event + */ + @Value + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + private static class EventListenersGroup implements Listener, EventExecutor { + + /** + * Plugin managing these event listeners + */ + @NonNull ListenerConfiguration configuration; + + /** + * Event listeners in this event listener group's dequeue + */ + @Getter(AccessLevel.NONE) @NonNull Deque> eventListeners = new ConcurrentLinkedDeque<>(); + + /** + * Adds the listener to the deque of handled listeners for the event. + * + * @param listener listener to add to handling dequeue + */ + private void addListener(final @NonNull Consumer listener) { + if (eventListeners.isEmpty()) { + PLUGIN_MANAGER.registerEvent( + configuration.getType(), this, configuration.getPriority(), this, configuration.getPlugin() + ); + LISTENERS_GROUPS.putIfAbsent(configuration, this); + } + + eventListeners.add(listener); + } + + /** + * Removes the listener from the deque of handled listeners for the event. + * + * @param listener listener to remove from handling dequeue + */ + private void removeListener(final @NonNull Consumer listener) { + eventListeners.remove(listener); + + if (eventListeners.isEmpty()) { + HandlerList.unregisterAll(this); + LISTENERS_GROUPS.remove(configuration); + } + } + + @Override + public void execute(final @NotNull Listener listener, final Event event) { + if (configuration.getType().isAssignableFrom(event.getClass())) { + @SuppressWarnings("unchecked") val castEvent = (E) event; + for (val eventListener : eventListeners) eventListener.accept(castEvent); + } + } + } + + @Value + private static class ListenerConfiguration { + @NonNull Plugin plugin; + @NonNull Class type; + @NonNull EventPriority priority; + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/item/ItemBuilder.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/item/ItemBuilder.java new file mode 100644 index 000000000..30103fc70 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/item/ItemBuilder.java @@ -0,0 +1,176 @@ +package ru.progrm_jarvis.minecraft.commons.item; + +import lombok.Data; +import lombok.NonNull; +import lombok.experimental.Accessors; +import lombok.val; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import ru.progrm_jarvis.minecraft.commons.enchant.Enchant; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +@Data(staticConstructor = "create") +@Accessors(fluent = true, chain = true) +public class ItemBuilder implements Cloneable { + + @NonNull Material material = Material.STONE; + int amount = 1; + + M metadata; + + // + + public Map enchantments() { + return metadata == null ? Collections.emptyMap() : new HashMap<>(metadata.getEnchants()); + } + + public ItemBuilder addEnchantment(final @NonNull Enchantment enchantment, final int level) { + initMetadata(); + + metadata.addEnchant(enchantment, level, true); + + return this; + } + + public ItemBuilder addEnchantment(final @NonNull Enchant enchant) { + initMetadata(); + + metadata.addEnchant(enchant.getEnchantment(), enchant.getLevel(), true); + + return this; + } + + public ItemBuilder addEnchantments(final @NonNull Enchant... enchants) { + initMetadata(); + + for (val enchant : enchants) metadata.addEnchant(enchant.getEnchantment(), enchant.getLevel(), true); + + return this; + } + + public ItemBuilder addEnchantments(final @NonNull Iterable enchants) { + initMetadata(); + + for (val enchant : enchants) metadata.addEnchant(enchant.getEnchantment(), enchant.getLevel(), true); + + return this; + } + + public ItemBuilder addEnchantments(final @NonNull Map enchantments) { + initMetadata(); + + for (val enchantment : enchantments.entrySet()) metadata.addEnchant( + enchantment.getKey(), enchantment.getValue(), true + ); + + return this; + } + + public ItemBuilder removeEnchantment(final @NonNull Enchantment enchantment) { + if (metadata != null) metadata.removeEnchant(enchantment); + + return this; + } + + public ItemBuilder removeEnchantments(final @NonNull Enchantment... enchantments) { + if (metadata != null) for (val enchantment : enchantments) metadata.removeEnchant(enchantment); + + return this; + } + + public ItemBuilder removeEnchantments(final @NonNull Iterable enchantments) { + if (metadata != null) for (val enchantment : enchantments) metadata.removeEnchant(enchantment); + + return this; + } + + // + + // + + @SuppressWarnings("unchecked") + protected void initMetadata() { + if (metadata == null) metadata = (M) Bukkit.getItemFactory().getItemMeta(material); + } + + @SuppressWarnings("unchecked") + public ItemBuilder metadata(final @NonNull M metadata) { + this.metadata = (M) metadata.clone(); + + return this; + } + + public ItemMeta metadata() { + initMetadata(); + + return metadata.clone(); + } + + public ItemBuilder metadata(final @NonNull Consumer metadataModifier) { + metadataModifier.accept(metadata); + + return this; + } + + // + + // + public ItemBuilder unbreakable(final boolean unbreakable) { + initMetadata(); + + metadata.setUnbreakable(unbreakable); + + return this; + } + + public boolean unbreakable() { + initMetadata(); + + return metadata.isUnbreakable(); + } + + public ItemBuilder displayName(final @NonNull String displayName) { + initMetadata(); + + metadata.setDisplayName(displayName); + + return this; + } + + public String displayName() { + initMetadata(); + + return metadata.getDisplayName(); + } + + public ItemBuilder localizedName(final @NonNull String localizedName) { + initMetadata(); + + metadata.setLocalizedName(localizedName); + + return this; + } + + public String localizedName() { + initMetadata(); + + return metadata.getLocalizedName(); + } + // + + public ItemStack build() { + initMetadata(); + + val item = new ItemStack(material, amount); + item.setItemMeta(metadata.clone()); + + return item; + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/item/ItemMetaBuilder.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/item/ItemMetaBuilder.java new file mode 100644 index 000000000..2b9696ef3 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/item/ItemMetaBuilder.java @@ -0,0 +1,160 @@ +package ru.progrm_jarvis.minecraft.commons.item; + +import lombok.*; +import lombok.experimental.Accessors; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Nullable; +import ru.progrm_jarvis.minecraft.commons.enchant.Enchant; + +import javax.annotation.OverridingMethodsMustInvokeSuper; +import java.util.HashMap; +import java.util.Map; + +@Data +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +@Accessors(fluent = true, chain = true) +public class ItemMetaBuilder> { + + protected static final ItemFactory ITEM_FACTORY = Bukkit.getItemFactory(); + + @Getter(AccessLevel.NONE) @SuppressWarnings("unchecked") protected final B self = (B) this; + + @Nullable protected Boolean unbreakable; + @Nullable protected String displayName; + @Nullable protected String localizedName; + Map enchantments; + + @Nullable + public Boolean unbreakable() {return this.unbreakable;} + + @Nullable + public String displayName() {return this.displayName;} + + @Nullable + public String localizedName() {return this.localizedName;} + + public Map enchantments() {return this.enchantments;} + + public B unbreakable(@Nullable Boolean unbreakable) { + this.unbreakable = unbreakable; + + return self; + } + + public B displayName(@Nullable String displayName) { + this.displayName = displayName; + + return self; + } + + public B localizedName(@Nullable String localizedName) { + this.localizedName = localizedName; + + return self; + } + + public B enchantments(Map enchantments) { + this.enchantments = enchantments; + + return self; + } + + // + + protected void initEnchantments() { + if (enchantments == null) enchantments = new HashMap<>(); + } + + public B addEnchantment(final @NonNull Enchantment enchantment, final int level) { + initEnchantments(); + + enchantments.put(enchantment, level); + + return self; + } + + public B addEnchantment(final @NonNull Enchant enchant) { + initEnchantments(); + + enchantments.put(enchant.getEnchantment(), enchant.getLevel()); + + return self; + } + + public B addEnchantments(final @NonNull Enchant... enchants) { + initEnchantments(); + + for (val enchant : enchants) enchantments.put(enchant.getEnchantment(), enchant.getLevel()); + + return self; + } + + public B addEnchantments(final @NonNull Iterable enchants) { + initEnchantments(); + + for (val enchant : enchants) enchantments.put(enchant.getEnchantment(), enchant.getLevel()); + + return self; + } + + public B addEnchantments(final @NonNull Map enchantments) { + initEnchantments(); + + for (val enchantment : enchantments.entrySet()) this.enchantments.put(enchantment.getKey(), enchantment.getValue()); + + return self; + } + + public B removeEnchantment(final @NonNull Enchantment enchantment) { + if (this.enchantments != null) enchantments.remove(enchantment); + + return self; + } + + public B removeEnchantments(final @NonNull Enchantment... enchantments) { + if (this.enchantments != null) for (val enchantment : enchantments) this.enchantments.remove(enchantment); + + return self; + } + + public B removeEnchantments(final @NonNull Iterable enchantments) { + if (this.enchantments != null) for (val enchantment : enchantments) this.enchantments.remove(enchantment); + + return self; + } + // + + public M build() { + @SuppressWarnings("unchecked") val meta = (M) ITEM_FACTORY.getItemMeta(Material.IRON_ORE); + if (meta == null) throw new UnsupportedOperationException("Cannot create ItemMeta"); + fillMeta(meta); + + return meta; + } + + public ItemMetaBuilder applyTo(@NonNull M itemMeta) { + fillMeta(itemMeta); + + return this; + } + + @OverridingMethodsMustInvokeSuper + protected void fillMeta(final @NonNull M meta) { + if (unbreakable != null) meta.setUnbreakable(unbreakable); + if (displayName != null) meta.setDisplayName(displayName); + if (localizedName != null) meta.setLocalizedName(localizedName); + if (enchantments != null) { + for (val enchantment : meta.getEnchants().keySet()) meta.removeEnchant(enchantment); + for (val enchantment : enchantments.entrySet()) meta + .addEnchant(enchantment.getKey(), enchantment.getValue(), true); + } + } + + public static > ItemMetaBuilder create() { + return new ItemMetaBuilder<>(); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/DefaultMapImage.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/DefaultMapImage.java new file mode 100644 index 000000000..dce968d83 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/DefaultMapImage.java @@ -0,0 +1,285 @@ +package ru.progrm_jarvis.minecraft.commons.mapimage; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.progrm_jarvis.javacommons.lazy.Lazy; +import ru.progrm_jarvis.minecraft.commons.util.hack.PreSuperCheck; + +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Consumer; + +import static com.google.common.base.Preconditions.checkArgument; +import static ru.progrm_jarvis.minecraft.commons.mapimage.MapImage.blankPixels; +import static ru.progrm_jarvis.minecraft.commons.mapimage.MapImageColor.NO_COLOR_CODE; + +/** + * The default {@link MapImage} implementation which stores its pixels as a 1-dimensional. + */ +@ToString +@EqualsAndHashCode +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class DefaultMapImage implements MapImage { + + /** + * {@code byte}-array of pixels of an image by X, Y indexes. + * A pixel can be accessed as {@code pixels[x + y * getWidth()]} + */ + byte[] pixels; + @Getter int width, height; + byte displayMode; + + /** + * Lazily initialized non-buffered drawer + */ + Lazy drawer = Lazy.create(Drawer::new); + + /** + * Lazily initialized buffered drawer + */ + Lazy bufferedDrawer = Lazy.create(BufferedDrawer::new); + + /** + * All subscribers active. + */ + Collection> updateSubscribers = new ArrayList<>(); + + /** + * Creates new map image from pixels. + * + * @param pixels array of Minecraft color IDs (columns of rows) + * @param displayMode possible map image display mode (from {@code 0} to {@code 4}) + */ + public DefaultMapImage(final byte[] pixels, final byte displayMode) { + this( + PreSuperCheck.beforeSuper(pixels, + () -> checkArgument(pixels.length == PIXELS_COUNT, "pixels length should be " + PIXELS_COUNT) + ), + WIDTH, HEIGHT, + PreSuperCheck.beforeSuper(displayMode, + () -> checkArgument( + displayMode >= 0 && displayMode <= 4, "displayMode should be between 0 and 4" + ) + ) + ); + } + + @Override + public byte getDisplay() { + return displayMode; + } + + @Override + public byte[] getMapData() { + return pixels; + } + + @Override + public byte[] getMapData(final int leastX, final int leastY, final int width, final int height) { + val data = new byte[width * height]; + var i = 0; + final int xBound = leastX + width, yBound = leastY + height; + for (var x = leastX; x < xBound; x++) for (var y = 0; y < yBound; y++) data[i] = pixels[x + y * width]; + + return data; + } + + /** + * Creates new map image from image. + * + * @param image from which to create the map image + * @param resize whether the image should be resized or cut to fit map image dimensions + * @param displayMode display mode of the image + * @return created map image + */ + public static MapImage from(final @NonNull BufferedImage image, final boolean resize, + final byte displayMode) { + return new DefaultMapImage(MapImages.getMapImagePixels(image, resize), displayMode); + } + + /////////////////////////////////////////////////////////////////////////// + // Updates and Subscriptions logic + /////////////////////////////////////////////////////////////////////////// + + @Override + public boolean isSubscribable() { + return true; + } + + @Override + public void subscribeOnUpdates(final Consumer subscriber) { + updateSubscribers.add(subscriber); + } + + @Override + public void unsubscribeFromUpdates(final Consumer subscriber) { + updateSubscribers.remove(subscriber); + } + + @Override + public void onUpdate(final @NonNull Delta delta) { + for (val updateSubscriber : updateSubscribers) updateSubscriber.accept(delta); + } + + /////////////////////////////////////////////////////////////////////////// + // Drawers + /////////////////////////////////////////////////////////////////////////// + + @Override + public MapImage.Drawer drawer() { + return drawer.get(); + } + + @Override + public MapImage.BufferedDrawer bufferedDrawer() { + return bufferedDrawer.get(); + } + + @ToString + @EqualsAndHashCode + protected final class Drawer implements MapImage.Drawer { + + @Override + public MapImage.Drawer px(final int x, final int y, final byte color) { + pixels[x + y * getWidth()] = color; + + return this; + } + + @Override + public MapImage.Drawer fill(final byte color) { + Arrays.fill(pixels, color); + onUpdate(Delta.of(pixels, width, 0, 0)); + + return this; + } + } + + /** + * Buffered drawer based on 2-dimensional {@code byte}-array of changed pixels and {@code int}-bounds. + */ + @Getter + @ToString + @EqualsAndHashCode + @FieldDefaults(level = AccessLevel.PROTECTED) + protected final class BufferedDrawer implements MapImage.BufferedDrawer { + + /** + * Array of changed pixels + */ + final byte[] buffer = blankPixels(new byte[DefaultMapImage.this.getWidth() * DefaultMapImage.this.getHeight()]); + + boolean unchanged = true; + + /** + * The least X-coordinate of changed image segment. + */ + int leastChangedX = Delta.NONE, + /** + * The least Y-coordinate of changed image segment. + */ + leastChangedY = Delta.NONE, + /** + * The most X-coordinate of changed image segment. + */ + mostChangedX = Delta.NONE, + /** + * The most Y-coordinate of changed image segment. + */ + mostChangedY = Delta.NONE; + + /** + * Resets this buffered drawer setting {@link #unchanged} to {@code true} and resetting its buffer. + */ + private void reset() { + unchanged = true; + leastChangedX = leastChangedY = mostChangedX = mostChangedY = Delta.NONE; + + blankPixels(buffer); + } + + @Override + public Delta dispose() { + // real disposal should happen only if there are changes + val delta = getDelta(); + + // perform image update only if delta is not empty (there are changes) + if (!delta.isEmpty()) { + final int width = delta.width(), height = delta.height(), + leastX = delta.leastX(), leastY = delta.leastY(); + + val pixels = delta.pixels(); + var i = -1; + for (var y = leastY; y < height; y++) { + val offset = y * width; + for (var x = leastX; x < width; x++) + if (pixels[++i] != NO_COLOR_CODE) DefaultMapImage.this + .pixels[x + offset] = pixels[i]; + } + + reset(); + + onUpdate(delta); + } + + return delta; + } + + @Override + public Delta getDelta() { + if (unchanged) return Delta.EMPTY; + + val width = mostChangedX - leastChangedX + 1; + val pixels = new byte[width * (mostChangedY - leastChangedY + 1)]; + var i = 0; + for (var y = leastChangedY; y < mostChangedY; y++) { + val offset = y * width; + for (var x = leastChangedX; x < mostChangedX; x++) pixels[i++] = buffer[x + offset]; + } + + return Delta.of(pixels, width, leastChangedX, leastChangedY); + } + + /////////////////////////////////////////////////////////////////////////// + // Drawing + /////////////////////////////////////////////////////////////////////////// + + @Override + public MapImage.Drawer px(final int x, final int y, final byte color) { + // put the changed pixel to the buffer + buffer[x + y * width] = color; + + // perform delta update if needed + // if it is the first update then the pixels is the zone of changes + if (unchanged) { + unchanged = false; + + leastChangedX = mostChangedX = x; + leastChangedY = mostChangedY = y; + } else { + if (x < leastChangedX) leastChangedX = x; + else if (x > mostChangedX) mostChangedX = x; + + if (y < leastChangedY) leastChangedY = y; + else if (y > mostChangedY) mostChangedY = y; + } + + return this; + } + + @Override + public MapImage.Drawer fill(final byte color) { + unchanged = false; + + Arrays.fill(buffer, color); + leastChangedX = leastChangedY = 0; + mostChangedX = WIDTH; + mostChangedY = HEIGHT; + + return this; + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImage.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImage.java new file mode 100644 index 000000000..4b1ec35f0 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImage.java @@ -0,0 +1,627 @@ +package ru.progrm_jarvis.minecraft.commons.mapimage; + +import lombok.*; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.function.Consumer; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Image on a map. + */ +public interface MapImage { + + /** + * Maximal width of a map allowed by Minecraft. + */ + int WIDTH = 128, + /** + * Maximal height of a map allowed by Minecraft. + */ + HEIGHT = 128, + /** + * Maximal (and only possible) amount of pixels on a map allowed by Minecraft. + */ + PIXELS_COUNT = WIDTH * HEIGHT; + + /** + * {@link #WIDTH} as {@code float} for coefficient calculations. + */ + float WIDTH_F = (float) WIDTH, + /** + * {@link #HEIGHT} as {@code float} for coefficient calculations + */ + HEIGHT_F = (float) HEIGHT; + + /** + * Gets display mode of the map image. + * + * @return value from {@code 0} (fully zoomed-in, 1 block/px) to {@code 4} (fully zoomed-out 16 blocks/px) + */ + byte getDisplay(); + + /** + * Gets the width of this map image. + * + * @return this map image's width + */ + int getWidth(); + + /** + * Gets the height of this map image. + * + * @return this map image's height + */ + int getHeight(); + + /** + * Gets 1-dimensional {@code byte}-array of this image map's pixels. + * + * @return this image's pixel data + * + * @apiNote order as {@link #getWidth()} {@code byte}s (columns) + * coming in a row {@link #getHeight()} times (once for each row) + */ + /* + From Minecraft Wiki: + colorID = Colors[widthOffset + heightOffset * width] ~~> color(x, y) = data[x + width * y] ~~> + ~~> / * x;y * / + 0;0 1;0 2;0, 0;1 1;1 2;1, 0;2 1;2 1;3 + */ + byte[] getMapData(); + + /** + * Gets 1-dimensional {@code byte}-array of this image map's pixels segment. + * + * @param leastX least X-coordinate of image segment + * @param leastY least Y-coordinate of image segment + * @param width width of the image segment + * @param height of image segment + * @return this image's pixel data segment + * + * @apiNote order as {@code width} {@code byte}s (columns) coming in a row {@code height} times (once for each row) + */ + byte[] getMapData(final int leastX, final int leastY, final int width, final int height); + + /** + * Gets 1-dimensional {@code byte}-array of this image map's pixels segment. + * + * @param delta delta for whose coordinates to get the image segment + * @return this image's pixel data segment + * + * @apiNote order as {@link Delta#width()} {@code byte}s (columns) + * coming in a row {@link Delta#height()} times (once for each row) + */ + default byte[] getMapData(final @NotNull Delta delta) { + return getMapData(delta.leastX(), delta.leastY(), delta.width(), delta.height()); + } + + /** + * Gets the non-buffered drawer for this image. + * + * @return non-buffered drawer for this image + */ + Drawer drawer(); + + /** + * Gets the buffered drawer for this image. + * + * @return buffered drawer for this image + */ + BufferedDrawer bufferedDrawer(); + + /** + * Handler for performed update (called whenever an update happens). + * + * @param delta delta of the image update + * @apiNote may not be called whenever there are no changes, but yet should normally handle empty deltas + */ + default void onUpdate(final @NonNull Delta delta) {} + + /** + * Checks whether this map image allows subscriptions on updates. + * + * @return {@code true} if this map image allows update subscriptions and {@code false} otherwise + */ + default boolean isSubscribable() { + return false; + } + + /** + * Subscribes on this image's updates. + * + * @param subscriber subscriber to be notified whenever an image is updated + * @throws UnsupportedOperationException if this map image doesn't allow update subscriptions + * + * @apiNote may not be called whenever there are no changes, but yet should normally handle empty deltas + * @implSpec may be unavailable, check {@link #isSubscribable()} before usage + */ + default void subscribeOnUpdates(final Consumer subscriber) { + throw new UnsupportedOperationException(getClass() + " doesn't support update subscriptions"); + } + + /** + * Unsubscribes from this image's updates. + * + * @param subscriber subscriber to stop being notified on image updates + * @throws UnsupportedOperationException if this map image doesn't allow update subscriptions + * + * @implSpec should do nothing if the callback is not subscribed + * @implSpec may be unavailable, check {@link #isSubscribable()} before usage + */ + default void unsubscribeFromUpdates(final Consumer subscriber) { + throw new UnsupportedOperationException(getClass() + " doesn't support update subscriptions"); + } + + /** + * Makes all pixels of the specified array blank ({@link MapImageColor#NO_COLOR_CODE}). + * + * @param pixels array of pixels to make blank + * @return passed array of pixels made blank + */ + static byte[] blankPixels(final byte[] pixels) { + Arrays.fill(pixels, MapImageColor.NO_COLOR_CODE); + + return pixels; + } + + /** + * An object responsible for changing map's content. + * + * @apiNote most non-primitive methods have default implementations based on primitive ones + */ + interface Drawer { + + /** + * Assures that the X-coordinate is inside the allowed bounds [{@code 0}; {@link MapImage#WIDTH}). + * + * @param x X-coordinate to check + * @throws IllegalArgumentException if the X-coordinate is not between the bounds + */ + static void checkX(final int x) { + if (x < 0) throw new IllegalArgumentException("X-coordinate should be non-negative"); + if (x >= WIDTH) throw new IllegalArgumentException("X-coordinate should be less than " + WIDTH); + } + + /** + * Assures that the Y-coordinate is inside the allowed bounds [{@code 0}; {@link MapImage#HEIGHT}). + * + * @param y Y-coordinate to check + * @throws IllegalArgumentException if the Y-coordinate is not between the bounds + */ + static void checkY(final int y) { + if (y < 0) throw new IllegalArgumentException("Y-coordinate should be non-negative"); + if (y >= HEIGHT) throw new IllegalArgumentException("Y-coordinate should be less than " + HEIGHT); + } + + /** + * Makes the specified X-coordinate surely be inside the allowed bounds [{@code 0}; {@link MapImage#WIDTH}). + * + * @param x X-coordinate to bound + * @return bounded X-coordinate + */ + static int boundX(final int x) { + if (x < 0) return 0; + if (x >= WIDTH) return WIDTH - 1; + return x; + } + + /** + * Makes the specified Y-coordinate surely be inside the allowed bounds [{@code 0}; {@link MapImage#HEIGHT}). + * + * @param y Y-coordinate to bound + * @return bounded Y-coordinate + */ + static int boundY(final int y) { + if (y < 0) return 0; + if (y >= HEIGHT) return HEIGHT - 1; + return y; + } + + /** + * Draws a pixel of the specified color at given coordinates. + * + * @param x X-coordinate to draw the pixel at + * @param y Y-coordinate to draw the pixel at + * @param color color of the pixel + * @return this drawer for chaining + */ + @Contract("_, _, _ -> this") + Drawer px(int x, int y, final byte color); + + /** + * Draws a line between two points. + * + * @param x1 the first point's X-coordinate + * @param y1 the first point's Y-coordinate + * @param x2 the second point's X-coordinate + * @param y2 the second point's Y-coordinate + * @param color color of the line + * @return this drawer for chaining + */ + @Contract("_, _, _, _, _ -> this") + @SuppressWarnings("Duplicates") // swapping + default Drawer line(int x1, int y1, int x2, int y2, final byte color) { + if (x1 > x2) { // swap x's + val oldX2 = x2; + x2 = x1; + x1 = oldX2; + } + + if (y1 > y2) { // swap y's + val oldY2 = y2; + y2 = y1; + y1 = oldY2; + } + + val dX = x2 - x1; + val dY = y2 - y1; + + // stepping should happen by the biggest delta to affect all rows / columns on it + if (dX > dY) { + // dX is bigger, step by it + val stepY = dY / (float) dX; + float y = y1; + for (/* use x1 for x */; x1 <= x2; x1++, y += stepY) px(x1, (int) y, color); + } else { + // dY is bigger or same, step by it + val stepX = dX / (float) dY; + float x = x1; + for (/* use y1 for y */; y1 <= y2; y1++, x += stepX) px((int) x, y1, color); + } + + return this; + } + + /** + * Draws a rectangle by given coordinates and color. + * + * @param x1 X-coordinate of the first rectangle point + * @param y1 Y-coordinate of the first rectangle point + * @param x2 X-coordinate of the second rectangle point + * @param y2 T-coordinate of the second rectangle point + * @param color color of the round + * @return this drawer for chaining + */ + @Contract("_, _, _, _, _ -> this") + @SuppressWarnings("Duplicates") // swapping + default Drawer rect(int x1, int y1, int x2, int y2, final byte color) { + checkX(x1); + checkY(y1); + checkX(x2); + checkY(y2); + + if (x1 > x2) { // swap x's + val oldX2 = x2; + x2 = x1; + x1 = oldX2; + } + + if (y1 > y2) { // swap y's + val oldY2 = y2; + y2 = y1; + y1 = oldY2; + } + + for (var x = x1; x <= x2; x++) for (var y = y1; y <= y2; y++) px(x, y, color); + + return this; + } + + /** + * Draws a round with the center specified of given radius and color. + * + * @param centerX X-coordinate of the round's center + * @param centerY Y-coordinate of the round's center + * @param radius radius of the round + * @param color color of the round + * @return this drawer for chaining + */ + @Contract("_, _, _, _ -> this") + default Drawer round(final int centerX, final int centerY, final int radius, final byte color) { + final int + minX = boundX(centerX - radius), maxX = boundX(centerX + radius), + minY = boundY(centerY - radius), maxY = boundY(centerY + radius); + + val squaredRadius = radius * radius; + + for (var x = minX; x <= maxX; x++) { + var squaredDX = x - centerX; // find delta + squaredDX *= squaredDX; // dy now stores a squared value + + for (var y = minY; y <= maxY; y++) { + var squaredDY = y - centerY; // find delta + squaredDY *= squaredDY; // dy now stores a squared value + + // Pythagoras theorem с² = a² + b² ~~> radius² = Δx² + Δy² + if (squaredRadius <= squaredDX + squaredDY) px(x, y, color); + } + } + + return this; + } + + /** + * Fills the image with the specified color. + * + * @param color color to fill the image with + * @return this drawer for chaining + * + * @apiNote this method doesn't provide default implementation because it is suboptmal in most cases + * to set similar pixels one by one. + * There is also no guarantees (although it is in most cases ) + */ + @Contract("_ -> this") + Drawer fill(byte color); + } + + /** + * A drawer which stores all changes releasing them only when required. + */ + interface BufferedDrawer extends Drawer { + + /** + * Gets the least X-coordinate of changed image segment. + * + * @return the least X-coordinate of changed image segment or {@link Delta#NONE} if no pixels were changed + */ + int getLeastChangedX(); + + /** + * Gets the least Y-coordinate of changed image segment. + * + * @return the least Y-coordinate of changed image segment or {@link Delta#NONE} if no pixels were changed + */ + int getLeastChangedY(); + + /** + * Gets the most X-coordinate of changed image segment. + * + * @return the most X-coordinate of changed image segment or {@link Delta#NONE} if no pixels were changed + */ + int getMostChangedX(); + + /** + * Gets the most Y-coordinate of changed image segment. + * + * @return the most Y-coordinate of changed image segment or {@link Delta#NONE} if no pixels were changed + */ + int getMostChangedY(); + + /** + * Gets the delta of the image which this drawer is having. + * + * @return delta of the image + */ + Delta getDelta(); + + /** + * Disposes the image. Disposal means applying all changes to the source Map image. + * This method should call source image's {@link #onUpdate(Delta)} with the actual delta + * whenever it is not empty. + * + * @return delta disposed + * @apiNote may not call {@link #onUpdate(Delta)} if the delta is empty + */ + Delta dispose(); + } + + /** + * Object containing data about changed image part. + * + * @see BufferedDrawer most common use-case of delta + */ + interface Delta { + + /** + * The value returned by {@link #leastX()} and {@link #leastY()} whenever there are no changes. + */ + int NONE = -1; + + /** + * The value returned by {@link #pixels()} whenever there are no changes. + */ + byte[] NO_PIXELS = new byte[0]; + + /** + * An empty delta. This should be used whenever there were no changes to the image. + */ + Empty EMPTY = new Empty(); + + /** + * Retrieves whether the delta is empty (there were no changes to the image) or not. + * + * @return {@code false} if at least one pixel differs from the image and {@code true} otherwise + */ + boolean isEmpty(); + + /** + * Gets the pixels changed (columns, rows). + * + * @return pixels changed or {@link #NO_PIXELS} if none were changed + */ + byte[] pixels(); + + /** + * Gets the least X-coordinate of the changed segment + * + * @return the least X-coordinate of the changed segment or {@link #NONE} if none were changed + */ + int leastX(); + + /** + * Gets the least Y-coordinate of the changed segment + * + * @return the least Y-coordinate of the changed segment or {@link #NONE} if none were changed + */ + int leastY(); + + /** + * Gets width of changed image segment + * + * @return width of changed image segment + */ + int width(); + + /** + * Gets height of changed image segment + * + * @return height of changed image segment + */ + int height(); + + /** + * Creates new delta. + * + * @param pixels pixels changed + * @param width width of image segment + * @param leastX least X-coordinate of the affected segment + * @param leastY least Y-coordinate of the affected segment + * @return empty delta if {@code pixels} is empty and non-empty delta otherwise + */ + static @NotNull Delta of(final byte[] pixels, final int width, final int leastX, final int leastY) { + val pixelsLength = pixels.length; + if (pixelsLength == 0) return EMPTY; + if (pixelsLength == 1) return new SinglePixel(pixels, leastX, leastY); + return new NonEmpty(pixels, width, leastX, leastY); + } + + /** + * Empty delta. There is no need to instantiate it for each empty delta, use {@link Delta#EMPTY} instead. + */ + @Value + class Empty implements Delta { + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public byte[] pixels() { + return NO_PIXELS; + } + + @Override + public int leastX() { + return -1; + } + + @Override + public int leastY() { + return -1; + } + + @Override + public int width() { + return 0; + } + + @Override + public int height() { + return 0; + } + } + + /** + * Non-empty delta. Usage of this class guarantees that it is not empty + * (its constructor does not perform checks of {@code pixels} emptiness and so can actually be empty). + */ + @Value + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE) + class NonEmpty implements Delta { + + byte[] pixels; + + int width, height; + int leastX, leastY; + + public NonEmpty(final byte[] pixels, final int width, final int leastX, final int leastY) { + checkArgument( + pixels.length % width == 0, "Length of pixels should be multiple of width (" + width + ")" + ); + checkArgument( + leastX >= 0 && leastX <= WIDTH, "leastX should be between 0 and " + WIDTH + ); + checkArgument( + leastY >= 0 && leastY <= HEIGHT, "leastX should be between 0 and " + HEIGHT + ); + + this.pixels = pixels; + this.width = width; + this.height = pixels.length / width; + this.leastX = leastX; + this.leastY = leastY; + } + + @Override + public boolean isEmpty() { + return pixels.length != 0; + } + + @Override + public int width() { + return width; + } + + @Override + public int height() { + return height; + } + } + + /** + * Delta affecting only one single pixel of an image. + */ + @Value + @Getter(AccessLevel.NONE) // not generate getter due to other names of fields + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + class SinglePixel implements Delta { + + byte[] pixel; + + int x, y; + + public SinglePixel(final byte color, final int x, final int y) { + this(new byte[]{color}, x, y); + } + + @Override + public byte[] pixels() { + return pixel; + } + + @Override + public int leastX() { + return x; + } + + @Override + public int leastY() { + return y; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public int width() { + return 1; + } + + @Override + public int height() { + return 1; + } + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImageColor.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImageColor.java new file mode 100644 index 000000000..145447199 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImageColor.java @@ -0,0 +1,405 @@ +package ru.progrm_jarvis.minecraft.commons.mapimage; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import it.unimi.dsi.fastutil.objects.Object2ByteMap; +import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; +import lombok.*; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +import org.bukkit.map.MapPalette; +import org.jetbrains.annotations.NotNull; +import ru.progrm_jarvis.minecraft.commons.util.BitwiseUtil; +import ru.progrm_jarvis.minecraft.commons.util.SystemPropertyUtil; +import ru.progrm_jarvis.minecraft.commons.util.image.ColorUtil; + +import java.awt.*; + +import static java.lang.Math.abs; + +/** + * A cached color which provides easy conversions between full 24-bit RGB and Minecraft Map colors. + * + * @apiNote Minecraft maps don't allow alpha channel + */ +@Value +@FieldDefaults(level = AccessLevel.PRIVATE) +@Accessors(fluent = true) +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class MapImageColor { + + /** + * A primitive constant value to use when there is no color code. + */ + public static final byte NO_COLOR_CODE = 0; + + private static final Object COLOR_IDS_CACHE_MUTEX = new Object[0]; + + private static final Cache COLOR_CACHE = CacheBuilder.newBuilder() + .weakValues() + .concurrencyLevel(SystemPropertyUtil.getSystemPropertyInt( + MapImageColor.class.getCanonicalName() + ".color-cache-concurrency-level", 2 + )) + .build(); + + /** + * All associations of color's with their available IDs. + */ + private static final Object2ByteMap COLOR_CODE_CACHE = new Object2ByteOpenHashMap<>(); + + /** + * 8 bits describing red part of the color + */ + byte red, + /** + * 8 bits describing green part of the color + */ + green, + /** + * 8 bits describing blue part of the color + */ + blue; + + /** + * An {@code int} representation of the color. Also used as the only field for hash-code generation. + */ + @EqualsAndHashCode.Include int rgb; + + /////////////////////////////////////////////////////////////////////////// + // Construction + /////////////////////////////////////////////////////////////////////////// + + /** + * Constructs a new map image color instance based on 3 base colors. + * This is an internal constructor as, normally, there should only exist one cached instance of any used color. + * + * @param red red color channel + * @param green green color channel + * @param blue blue color channel + */ + private MapImageColor(final byte red, final byte green, final byte blue) { + this.red = red; + this.green = green; + this.blue = blue; + + this.rgb = ColorUtil.toArgb(red, green, blue); + } + + /** + * Constructs a new map image color instance based on 3 base colors. + * This is an internal constructor as, normally, there should only exist one cached instance of any used color. + * + * @param red red color channel + * @param green green color channel + * @param blue blue color channel + */ + private MapImageColor(final int red, final int green, final int blue) { + this((byte) red, (byte) green, (byte) blue); + } + + /////////////////////////////////////////////////////////////////////////// + // Conversions + /////////////////////////////////////////////////////////////////////////// + + /** + * Creates new map image color from specified {@link java.awt} {@link Color}. + * + * @param color color to convert to map image color object + * @return map image color equivalent of specified color object + */ + public static @NotNull MapImageColor from(final @NonNull Color color) { + return of(color.getRed(), color.getGreen(), color.getBlue()); + } + + /** + * Gets or creates cached map image color from specified {@code int}-RGB. + * + * @param rgb RGB encoded as {@code int} + * @return cached or created and cached map image color + */ + @SneakyThrows + public static @NotNull MapImageColor of(final int rgb) { + return COLOR_CACHE.get(rgb, () -> new MapImageColor(ColorUtil.red(rgb), ColorUtil.green(rgb), ColorUtil.blue(rgb))); + } + + /** + * Gets or creates cached map image color from specified color divided on color channels. + * + * @param red red color channel + * @param green green color channel + * @param blue blue color channel + * @return cached or created and cached map image color + */ + @SneakyThrows + @NotNull public static MapImageColor of(final byte red, final byte green, final byte blue) { + return COLOR_CACHE.get(ColorUtil.toArgb(red, green, blue), () -> new MapImageColor(red, green, blue)); + } + + /** + * Gets or creates cached map image color from specified color divided on color channels. + * + * @param red red color channel + * @param green green color channel + * @param blue blue color channel + * @return cached or created and cached map image color + * + * @apiNote alias {@link #of(byte, byte, byte)} using {@code int}s not to perform casts in method call + */ + public static @NotNull MapImageColor of(final int red, final int green, final int blue) { + return of((byte) red, (byte) green, (byte) blue); + } + + /** + * Gets the id of the color closest to the one given. This value is cached for further usage. + * + * @param color color for which to find the closest available color code + * @return closest available color code + * + * @implNote uses {@link MapPalette#matchColor(Color)} because (although it is deprecated) it is the simplest way + */ + @SneakyThrows + public static byte getClosestColorCode(final MapImageColor color) { + if (COLOR_CODE_CACHE.containsKey(color)) return COLOR_CODE_CACHE.get(color); + + // the value which will store the color code + final byte colorCode; + synchronized (COLOR_IDS_CACHE_MUTEX) { + //noinspection deprecation ( use of MapPalette#matchColor(..) + COLOR_CODE_CACHE.put(color, colorCode = MapPalette.matchColor(new Color(color.rgb))); + } + + return colorCode; + } + + /** + * Gets the id of the color closest to the one given by calculating + * dissimilarity rate of each available with the one given. + * This value is cached for further usage. + * + * @param rgb RGB-color {@code int} for which to find the closest available color code + * @return closest available color id + */ + @SneakyThrows + public static byte getClosestColorCode(final int rgb) { + return getClosestColorCode(of(rgb)); + } + + /////////////////////////////////////////////////////////////////////////// + // Difference counting + /////////////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////////////// + // Distance squared (as if colors were 3 axises) + /////////////////////////////////////////////////////////////////////////// + + public static int getDistanceSquared(final int red1, final int green1, final int blue1, + final int red2, final int green2, final int blue2) { + final int dRed = red2 - red1, dGreen = green2 - green1, dBlue = blue2 - blue1; + + return dRed * dRed + dBlue * dBlue + dGreen * dGreen; + } + + public static int getDistanceSquared(final byte red1, final byte green1, final byte blue1, + final byte red2, final byte green2, final byte blue2) { + val dRed = BitwiseUtil.byteToUnsignedInt(red2) - BitwiseUtil.byteToUnsignedInt(red1); + val dGreen = BitwiseUtil.byteToUnsignedInt(green2) - BitwiseUtil.byteToUnsignedInt(green1); + val dBlue = BitwiseUtil.byteToUnsignedInt(blue2) - BitwiseUtil.byteToUnsignedInt(blue1); + + return getDistanceSquared( + BitwiseUtil.byteToUnsignedInt(red1), + BitwiseUtil.byteToUnsignedInt(green1), + BitwiseUtil.byteToUnsignedInt(blue1), + BitwiseUtil.byteToUnsignedInt(red2), + BitwiseUtil.byteToUnsignedInt(green2), + BitwiseUtil.byteToUnsignedInt(blue2) + ); + } + + public static int getDistanceSquared(final @NonNull MapImageColor color1, final @NonNull MapImageColor color2) { + return getDistanceSquared(color1.red, color1.green, color1.blue, color2.red, color2.green, color2.blue); + } + + public static int getDistanceSquared(final int rgb1, final int rgb2) { + return getDistanceSquared( + ColorUtil.red(rgb1), ColorUtil.green(rgb1), ColorUtil.blue(rgb1), + ColorUtil.red(rgb2), ColorUtil.green(rgb2), ColorUtil.blue(rgb2) + ); + } + + public int getDistanceSquared(final byte red, final byte green, final byte blue) { + return getDistanceSquared(this.red, this.green, this.blue, red, green, blue); + } + + public int getDistanceSquared(final int red, final int green, final int blue) { + return getDistanceSquared( + BitwiseUtil.byteToUnsignedInt(this.red), + BitwiseUtil.byteToUnsignedInt(this.green), + BitwiseUtil.byteToUnsignedInt(this.blue), + red, green, blue); + } + + public int getDistanceSquared(final @NonNull MapImageColor other) { + return getDistanceSquared(red, green, blue, other.red, other.green, other.blue); + } + + public int getDistanceSquared(final int rgb) { + return getDistanceSquared(ColorUtil.red(rgb), ColorUtil.green(rgb), ColorUtil.blue(rgb)); + } + + /////////////////////////////////////////////////////////////////////////// + // Sum of channels + /////////////////////////////////////////////////////////////////////////// + + public static int getSum(final int red1, final int green1, final int blue1, + final int red2, final int green2, final int blue2) { + return abs(red2 - red1) + abs(green2 - green1) + abs(blue2 - blue1); + } + + public static int getSum(final byte red1, final byte green1, final byte blue1, + final byte red2, final byte green2, final byte blue2) { + return getSum( + BitwiseUtil.byteToUnsignedInt(red1), + BitwiseUtil.byteToUnsignedInt(green1), + BitwiseUtil.byteToUnsignedInt(blue1), + BitwiseUtil.byteToUnsignedInt(red2), + BitwiseUtil.byteToUnsignedInt(green2), + BitwiseUtil.byteToUnsignedInt(blue2) + ); + } + + public static int getSum(final @NonNull MapImageColor color1, final @NonNull MapImageColor color2) { + return getSum(color1.red, color1.green, color1.blue, color2.red, color2.green, color2.blue); + } + + public static int getSum(final int rgb1, final int rgb2) { + return getSum( + ColorUtil.red(rgb1), ColorUtil.green(rgb1), ColorUtil.blue(rgb1), + ColorUtil.red(rgb2), ColorUtil.green(rgb2), ColorUtil.blue(rgb2) + ); + } + + public int getSum(final byte red, final byte green, final byte blue) { + return getSum(this.red, this.green, this.blue, red, green, blue); + } + + public int getSum(final int red, final int green, final int blue) { + return getSum( + BitwiseUtil.byteToUnsignedInt(this.red), + BitwiseUtil.byteToUnsignedInt(this.green), + BitwiseUtil.byteToUnsignedInt(this.blue), + red, green, blue + ); + } + + public int getSum(final @NonNull MapImageColor other) { + return getSum(red, green, blue, other.red, other.green, other.blue); + } + + public int getSum(final int rgb) { + return getSum(ColorUtil.red(rgb), ColorUtil.green(rgb), ColorUtil.blue(rgb)); + } + + /////////////////////////////////////////////////////////////////////////// + // Multiplication product of channels + /////////////////////////////////////////////////////////////////////////// + + public static int getMultiplicationProduct(final int red1, final int green1, final int blue1, + final int red2, final int green2, final int blue2) { + return (red2 - red1) * (green2 - green1) * (blue2 - blue1); + } + + public static int getMultiplicationProduct(final byte red1, final byte green1, final byte blue1, + final byte red2, final byte green2, final byte blue2) { + return (BitwiseUtil.byteToUnsignedInt(red2) - BitwiseUtil.byteToUnsignedInt(red1)) + * (BitwiseUtil.byteToUnsignedInt(green2) - BitwiseUtil.byteToUnsignedInt(green1)) + * (BitwiseUtil.byteToUnsignedInt(blue2) - BitwiseUtil.byteToUnsignedInt(blue1)); + } + + public static int getMultiplicationProduct(final @NonNull MapImageColor color1, final @NonNull MapImageColor color2) { + return getMultiplicationProduct(color1.red, color1.green, color1.blue, color2.red, color2.green, color2.blue); + } + + public static int getMultiplicationProduct(final int rgb1, final int rgb2) { + return getMultiplicationProduct( + ColorUtil.red(rgb1), ColorUtil.green(rgb1), ColorUtil.blue(rgb1), + ColorUtil.red(rgb2), ColorUtil.green(rgb2), ColorUtil.blue(rgb2) + ); + } + + public int getMultiplicationProduct(final int red, final int green, final int blue) { + return getMultiplicationProduct( + BitwiseUtil.byteToUnsignedInt(this.red), + BitwiseUtil.byteToUnsignedInt(this.green), + BitwiseUtil.byteToUnsignedInt(this.blue), + red, green, blue + ); + } + + public int getMultiplicationProduct(final byte red, final byte green, final byte blue) { + return getMultiplicationProduct(this.red, this.green, this.blue, red, green, blue); + } + + public int getMultiplicationProduct(final @NonNull MapImageColor other) { + return getMultiplicationProduct(red, green, blue, other.red, other.green, other.blue); + } + + public int getMultiplicationProduct(final int rgb) { + return getMultiplicationProduct(ColorUtil.red(rgb), ColorUtil.green(rgb), ColorUtil.blue(rgb)); + } + + /////////////////////////////////////////////////////////////////////////// + // Natural distance + // Due to nature of human's eyes it is more accurate to use + // unequal coefficients for color-channels to be more accurate + // (red) = 0.3 + // (green) = 0.59 + // (blue) = 0.11 + /////////////////////////////////////////////////////////////////////////// + + public static int getNaturalDistanceSquared(final byte red1, final byte green1, final byte blue1, + final byte red2, final byte green2, final byte blue2) { + val dRed = (BitwiseUtil.byteToUnsignedInt(red2) - BitwiseUtil.byteToUnsignedInt(red1)) * 0.3; + val dGreen = (BitwiseUtil.byteToUnsignedInt(green2) - BitwiseUtil.byteToUnsignedInt(green1)) * 0.59; + val dBlue = (BitwiseUtil.byteToUnsignedInt(blue2) - BitwiseUtil.byteToUnsignedInt(blue1)) * 0.11; + + return (int) (dRed * dRed + dBlue * dBlue + dGreen * dGreen); + } + + public static int getNaturalDistanceSquared(final int red1, final int green1, final int blue1, + final int red2, final int green2, final int blue2) { + final double dRed = (red2 - red1) * 0.3, dGreen = (green2 - green1) * 0.59, dBlue = (blue2 - blue1) * 0.11; + + return (int) (dRed * dRed + dBlue * dBlue + dGreen * dGreen); + } + + public static int getNaturalDistanceSquared(final @NonNull MapImageColor color1, final @NonNull MapImageColor color2) { + return getNaturalDistanceSquared(color1.red, color1.green, color1.blue, color2.red, color2.green, color2.blue); + } + + public static int getNaturalDistanceSquared(final int rgb1, final int rgb2) { + return getNaturalDistanceSquared( + ColorUtil.red(rgb1), ColorUtil.green(rgb1), ColorUtil.blue(rgb1), + ColorUtil.red(rgb2), ColorUtil.green(rgb2), ColorUtil.blue(rgb2) + ); + } + + public int getNaturalDistanceSquared(final int red, final int green, final int blue) { + return getNaturalDistanceSquared( + BitwiseUtil.byteToUnsignedInt(this.red), + BitwiseUtil.byteToUnsignedInt(this.green), + BitwiseUtil.byteToUnsignedInt(this.blue), + red, green, blue); + } + + public int getNaturalDistanceSquared(final byte red, final byte green, final byte blue) { + return getNaturalDistanceSquared(this.red, this.green, this.blue, red, green, blue); + } + + public int getNaturalDistanceSquared(final @NonNull MapImageColor other) { + return getNaturalDistanceSquared(red, green, blue, other.red, other.green, other.blue); + } + + public int getNaturalDistanceSquared(final int rgb) { + return getNaturalDistanceSquared(ColorUtil.red(rgb), ColorUtil.green(rgb), ColorUtil.blue(rgb)); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImageMinecraftColors.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImageMinecraftColors.java new file mode 100644 index 000000000..2e60c977d --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImageMinecraftColors.java @@ -0,0 +1,305 @@ +package ru.progrm_jarvis.minecraft.commons.mapimage; + +import it.unimi.dsi.fastutil.ints.Int2ByteMap; +import it.unimi.dsi.fastutil.ints.Int2ByteMaps; +import it.unimi.dsi.fastutil.ints.Int2ByteOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import lombok.experimental.UtilityClass; +import lombok.val; +import ru.progrm_jarvis.minecraft.commons.util.image.ColorUtil; + +import static ru.progrm_jarvis.minecraft.commons.mapimage.MapImageColor.NO_COLOR_CODE; + +/** + * Utilities related to Minecraft color codes for a map. + */ +@UtilityClass +public class MapImageMinecraftColors { + + /** + * All available colors available in Minecraft associated with their {@code byte}-codes + * @see Taken from Minecraft Wiki + */ + public final Int2ByteMap MINECRAFT_RGB_COLOR_CODES; + + /** + * All available colors available in Minecraft + *

+ * @see Taken from Minecraft Wiki + */ + private final IntSet MINECRAFT_RGB_COLORS; + + static { + // + val minecraftColors = new Int2ByteOpenHashMap(NO_COLOR_CODE); + minecraftColors.put(ColorUtil.toArgb((byte) 89, (byte) 125, (byte) 39), (byte) 4); + minecraftColors.put(ColorUtil.toArgb((byte) 109, (byte) 153, (byte) 48), (byte) 5); + minecraftColors.put(ColorUtil.toArgb((byte) 127, (byte) 178, (byte) 56), (byte) 6); + minecraftColors.put(ColorUtil.toArgb((byte) 67, (byte) 94, (byte) 29), (byte) 7); + minecraftColors.put(ColorUtil.toArgb((byte) 174, (byte) 164, (byte) 115), (byte) 8); + minecraftColors.put(ColorUtil.toArgb((byte) 213, (byte) 201, (byte) 140), (byte) 9); + minecraftColors.put(ColorUtil.toArgb((byte) 247, (byte) 233, (byte) 163), (byte) 10); + minecraftColors.put(ColorUtil.toArgb((byte) 130, (byte) 123, (byte) 86), (byte) 11); + minecraftColors.put(ColorUtil.toArgb((byte) 140, (byte) 140, (byte) 140), (byte) 12); + minecraftColors.put(ColorUtil.toArgb((byte) 171, (byte) 171, (byte) 171), (byte) 13); + minecraftColors.put(ColorUtil.toArgb((byte) 199, (byte) 199, (byte) 199), (byte) 14); + minecraftColors.put(ColorUtil.toArgb((byte) 105, (byte) 105, (byte) 105), (byte) 15); + minecraftColors.put(ColorUtil.toArgb((byte) 180, (byte) 0, (byte) 0), (byte) 16); + minecraftColors.put(ColorUtil.toArgb((byte) 220, (byte) 0, (byte) 0), (byte) 17); + minecraftColors.put(ColorUtil.toArgb((byte) 255, (byte) 0, (byte) 0), (byte) 18); + minecraftColors.put(ColorUtil.toArgb((byte) 135, (byte) 0, (byte) 0), (byte) 19); + minecraftColors.put(ColorUtil.toArgb((byte) 112, (byte) 112, (byte) 180), (byte) 20); + minecraftColors.put(ColorUtil.toArgb((byte) 138, (byte) 138, (byte) 220), (byte) 21); + minecraftColors.put(ColorUtil.toArgb((byte) 160, (byte) 160, (byte) 255), (byte) 22); + minecraftColors.put(ColorUtil.toArgb((byte) 84, (byte) 84, (byte) 135), (byte) 23); + minecraftColors.put(ColorUtil.toArgb((byte) 117, (byte) 117, (byte) 117), (byte) 24); + minecraftColors.put(ColorUtil.toArgb((byte) 144, (byte) 144, (byte) 144), (byte) 25); + minecraftColors.put(ColorUtil.toArgb((byte) 167, (byte) 167, (byte) 167), (byte) 26); + minecraftColors.put(ColorUtil.toArgb((byte) 88, (byte) 88, (byte) 88), (byte) 27); + minecraftColors.put(ColorUtil.toArgb((byte) 0, (byte) 87, (byte) 0), (byte) 28); + minecraftColors.put(ColorUtil.toArgb((byte) 0, (byte) 106, (byte) 0), (byte) 29); + minecraftColors.put(ColorUtil.toArgb((byte) 0, (byte) 124, (byte) 0), (byte) 30); + minecraftColors.put(ColorUtil.toArgb((byte) 0, (byte) 65, (byte) 0), (byte) 31); + minecraftColors.put(ColorUtil.toArgb((byte) 180, (byte) 180, (byte) 180), (byte) 32); + minecraftColors.put(ColorUtil.toArgb((byte) 220, (byte) 220, (byte) 220), (byte) 33); + minecraftColors.put(ColorUtil.toArgb((byte) 255, (byte) 255, (byte) 255), (byte) 34); + minecraftColors.put(ColorUtil.toArgb((byte) 135, (byte) 135, (byte) 135), (byte) 35); + minecraftColors.put(ColorUtil.toArgb((byte) 115, (byte) 118, (byte) 129), (byte) 36); + minecraftColors.put(ColorUtil.toArgb((byte) 141, (byte) 144, (byte) 158), (byte) 37); + minecraftColors.put(ColorUtil.toArgb((byte) 164, (byte) 168, (byte) 184), (byte) 38); + minecraftColors.put(ColorUtil.toArgb((byte) 86, (byte) 88, (byte) 97), (byte) 39); + minecraftColors.put(ColorUtil.toArgb((byte) 106, (byte) 76, (byte) 54), (byte) 40); + minecraftColors.put(ColorUtil.toArgb((byte) 130, (byte) 94, (byte) 66), (byte) 41); + minecraftColors.put(ColorUtil.toArgb((byte) 151, (byte) 109, (byte) 77), (byte) 42); + minecraftColors.put(ColorUtil.toArgb((byte) 79, (byte) 57, (byte) 40), (byte) 43); + minecraftColors.put(ColorUtil.toArgb((byte) 79, (byte) 79, (byte) 79), (byte) 44); + minecraftColors.put(ColorUtil.toArgb((byte) 96, (byte) 96, (byte) 96), (byte) 45); + minecraftColors.put(ColorUtil.toArgb((byte) 112, (byte) 112, (byte) 112), (byte) 46); + minecraftColors.put(ColorUtil.toArgb((byte) 59, (byte) 59, (byte) 59), (byte) 47); + minecraftColors.put(ColorUtil.toArgb((byte) 45, (byte) 45, (byte) 180), (byte) 48); + minecraftColors.put(ColorUtil.toArgb((byte) 55, (byte) 55, (byte) 220), (byte) 49); + minecraftColors.put(ColorUtil.toArgb((byte) 64, (byte) 64, (byte) 255), (byte) 50); + minecraftColors.put(ColorUtil.toArgb((byte) 33, (byte) 33, (byte) 135), (byte) 51); + minecraftColors.put(ColorUtil.toArgb((byte) 100, (byte) 84, (byte) 50), (byte) 52); + minecraftColors.put(ColorUtil.toArgb((byte) 123, (byte) 102, (byte) 62), (byte) 53); + minecraftColors.put(ColorUtil.toArgb((byte) 143, (byte) 119, (byte) 72), (byte) 54); + minecraftColors.put(ColorUtil.toArgb((byte) 75, (byte) 63, (byte) 38), (byte) 55); + minecraftColors.put(ColorUtil.toArgb((byte) 180, (byte) 177, (byte) 172), (byte) 56); + minecraftColors.put(ColorUtil.toArgb((byte) 220, (byte) 217, (byte) 211), (byte) 57); + minecraftColors.put(ColorUtil.toArgb((byte) 255, (byte) 252, (byte) 245), (byte) 58); + minecraftColors.put(ColorUtil.toArgb((byte) 135, (byte) 133, (byte) 129), (byte) 59); + minecraftColors.put(ColorUtil.toArgb((byte) 152, (byte) 89, (byte) 36), (byte) 60); + minecraftColors.put(ColorUtil.toArgb((byte) 186, (byte) 109, (byte) 44), (byte) 61); + minecraftColors.put(ColorUtil.toArgb((byte) 216, (byte) 127, (byte) 51), (byte) 62); + minecraftColors.put(ColorUtil.toArgb((byte) 114, (byte) 67, (byte) 27), (byte) 63); + minecraftColors.put(ColorUtil.toArgb((byte) 125, (byte) 53, (byte) 152), (byte) 64); + minecraftColors.put(ColorUtil.toArgb((byte) 153, (byte) 65, (byte) 186), (byte) 65); + minecraftColors.put(ColorUtil.toArgb((byte) 178, (byte) 76, (byte) 216), (byte) 66); + minecraftColors.put(ColorUtil.toArgb((byte) 94, (byte) 40, (byte) 114), (byte) 67); + minecraftColors.put(ColorUtil.toArgb((byte) 72, (byte) 108, (byte) 152), (byte) 68); + minecraftColors.put(ColorUtil.toArgb((byte) 88, (byte) 132, (byte) 186), (byte) 69); + minecraftColors.put(ColorUtil.toArgb((byte) 102, (byte) 153, (byte) 216), (byte) 70); + minecraftColors.put(ColorUtil.toArgb((byte) 54, (byte) 81, (byte) 114), (byte) 71); + minecraftColors.put(ColorUtil.toArgb((byte) 161, (byte) 161, (byte) 36), (byte) 72); + minecraftColors.put(ColorUtil.toArgb((byte) 197, (byte) 197, (byte) 44), (byte) 73); + minecraftColors.put(ColorUtil.toArgb((byte) 229, (byte) 229, (byte) 51), (byte) 74); + minecraftColors.put(ColorUtil.toArgb((byte) 121, (byte) 121, (byte) 27), (byte) 75); + minecraftColors.put(ColorUtil.toArgb((byte) 89, (byte) 144, (byte) 17), (byte) 76); + minecraftColors.put(ColorUtil.toArgb((byte) 109, (byte) 176, (byte) 21), (byte) 77); + minecraftColors.put(ColorUtil.toArgb((byte) 127, (byte) 204, (byte) 25), (byte) 78); + minecraftColors.put(ColorUtil.toArgb((byte) 67, (byte) 108, (byte) 13), (byte) 79); + minecraftColors.put(ColorUtil.toArgb((byte) 170, (byte) 89, (byte) 116), (byte) 80); + minecraftColors.put(ColorUtil.toArgb((byte) 208, (byte) 109, (byte) 142), (byte) 81); + minecraftColors.put(ColorUtil.toArgb((byte) 242, (byte) 127, (byte) 165), (byte) 82); + minecraftColors.put(ColorUtil.toArgb((byte) 128, (byte) 67, (byte) 87), (byte) 83); + minecraftColors.put(ColorUtil.toArgb((byte) 53, (byte) 53, (byte) 53), (byte) 84); + minecraftColors.put(ColorUtil.toArgb((byte) 65, (byte) 65, (byte) 65), (byte) 85); + minecraftColors.put(ColorUtil.toArgb((byte) 76, (byte) 76, (byte) 76), (byte) 86); + minecraftColors.put(ColorUtil.toArgb((byte) 40, (byte) 40, (byte) 40), (byte) 87); + minecraftColors.put(ColorUtil.toArgb((byte) 108, (byte) 108, (byte) 108), (byte) 88); + minecraftColors.put(ColorUtil.toArgb((byte) 132, (byte) 132, (byte) 132), (byte) 89); + minecraftColors.put(ColorUtil.toArgb((byte) 153, (byte) 153, (byte) 153), (byte) 90); + minecraftColors.put(ColorUtil.toArgb((byte) 81, (byte) 81, (byte) 81), (byte) 91); + minecraftColors.put(ColorUtil.toArgb((byte) 53, (byte) 89, (byte) 108), (byte) 92); + minecraftColors.put(ColorUtil.toArgb((byte) 65, (byte) 109, (byte) 132), (byte) 93); + minecraftColors.put(ColorUtil.toArgb((byte) 76, (byte) 127, (byte) 153), (byte) 94); + minecraftColors.put(ColorUtil.toArgb((byte) 40, (byte) 67, (byte) 81), (byte) 95); + minecraftColors.put(ColorUtil.toArgb((byte) 89, (byte) 44, (byte) 125), (byte) 96); + minecraftColors.put(ColorUtil.toArgb((byte) 109, (byte) 54, (byte) 153), (byte) 97); + minecraftColors.put(ColorUtil.toArgb((byte) 127, (byte) 63, (byte) 178), (byte) 98); + minecraftColors.put(ColorUtil.toArgb((byte) 67, (byte) 33, (byte) 94), (byte) 99); + minecraftColors.put(ColorUtil.toArgb((byte) 36, (byte) 53, (byte) 125), (byte) 100); + minecraftColors.put(ColorUtil.toArgb((byte) 44, (byte) 65, (byte) 153), (byte) 101); + minecraftColors.put(ColorUtil.toArgb((byte) 51, (byte) 76, (byte) 178), (byte) 102); + minecraftColors.put(ColorUtil.toArgb((byte) 27, (byte) 40, (byte) 94), (byte) 103); + minecraftColors.put(ColorUtil.toArgb((byte) 72, (byte) 53, (byte) 36), (byte) 104); + minecraftColors.put(ColorUtil.toArgb((byte) 88, (byte) 65, (byte) 44), (byte) 105); + minecraftColors.put(ColorUtil.toArgb((byte) 102, (byte) 76, (byte) 51), (byte) 106); + minecraftColors.put(ColorUtil.toArgb((byte) 54, (byte) 40, (byte) 27), (byte) 107); + minecraftColors.put(ColorUtil.toArgb((byte) 72, (byte) 89, (byte) 36), (byte) 108); + minecraftColors.put(ColorUtil.toArgb((byte) 88, (byte) 109, (byte) 44), (byte) 109); + minecraftColors.put(ColorUtil.toArgb((byte) 102, (byte) 127, (byte) 51), (byte) 110); + minecraftColors.put(ColorUtil.toArgb((byte) 54, (byte) 67, (byte) 27), (byte) 111); + minecraftColors.put(ColorUtil.toArgb((byte) 108, (byte) 36, (byte) 36), (byte) 112); + minecraftColors.put(ColorUtil.toArgb((byte) 132, (byte) 44, (byte) 44), (byte) 113); + minecraftColors.put(ColorUtil.toArgb((byte) 153, (byte) 51, (byte) 51), (byte) 114); + minecraftColors.put(ColorUtil.toArgb((byte) 81, (byte) 27, (byte) 27), (byte) 115); + minecraftColors.put(ColorUtil.toArgb((byte) 17, (byte) 17, (byte) 17), (byte) 116); + minecraftColors.put(ColorUtil.toArgb((byte) 21, (byte) 21, (byte) 21), (byte) 117); + minecraftColors.put(ColorUtil.toArgb((byte) 25, (byte) 25, (byte) 25), (byte) 118); + minecraftColors.put(ColorUtil.toArgb((byte) 13, (byte) 13, (byte) 13), (byte) 119); + minecraftColors.put(ColorUtil.toArgb((byte) 176, (byte) 168, (byte) 54), (byte) 120); + minecraftColors.put(ColorUtil.toArgb((byte) 215, (byte) 205, (byte) 66), (byte) 121); + minecraftColors.put(ColorUtil.toArgb((byte) 250, (byte) 238, (byte) 77), (byte) 122); + minecraftColors.put(ColorUtil.toArgb((byte) 132, (byte) 126, (byte) 40), (byte) 123); + minecraftColors.put(ColorUtil.toArgb((byte) 64, (byte) 154, (byte) 150), (byte) 124); + minecraftColors.put(ColorUtil.toArgb((byte) 79, (byte) 188, (byte) 183), (byte) 125); + minecraftColors.put(ColorUtil.toArgb((byte) 92, (byte) 219, (byte) 213), (byte) 126); + minecraftColors.put(ColorUtil.toArgb((byte) 48, (byte) 115, (byte) 112), (byte) 127); + minecraftColors.put(ColorUtil.toArgb((byte) 52, (byte) 90, (byte) 180), (byte) 128); + minecraftColors.put(ColorUtil.toArgb((byte) 63, (byte) 110, (byte) 220), (byte) 129); + minecraftColors.put(ColorUtil.toArgb((byte) 74, (byte) 128, (byte) 255), (byte) 130); + minecraftColors.put(ColorUtil.toArgb((byte) 39, (byte) 67, (byte) 135), (byte) 131); + minecraftColors.put(ColorUtil.toArgb((byte) 0, (byte) 153, (byte) 40), (byte) 132); + minecraftColors.put(ColorUtil.toArgb((byte) 0, (byte) 187, (byte) 50), (byte) 133); + minecraftColors.put(ColorUtil.toArgb((byte) 0, (byte) 217, (byte) 58), (byte) 134); + minecraftColors.put(ColorUtil.toArgb((byte) 0, (byte) 114, (byte) 30), (byte) 135); + minecraftColors.put(ColorUtil.toArgb((byte) 91, (byte) 60, (byte) 34), (byte) 136); + minecraftColors.put(ColorUtil.toArgb((byte) 111, (byte) 74, (byte) 42), (byte) 137); + minecraftColors.put(ColorUtil.toArgb((byte) 129, (byte) 86, (byte) 49), (byte) 138); + minecraftColors.put(ColorUtil.toArgb((byte) 68, (byte) 45, (byte) 25), (byte) 139); + minecraftColors.put(ColorUtil.toArgb((byte) 79, (byte) 1, (byte) 0), (byte) 140); + minecraftColors.put(ColorUtil.toArgb((byte) 96, (byte) 1, (byte) 0), (byte) 141); + minecraftColors.put(ColorUtil.toArgb((byte) 112, (byte) 2, (byte) 0), (byte) 142); + minecraftColors.put(ColorUtil.toArgb((byte) 59, (byte) 1, (byte) 0), (byte) 143); + minecraftColors.put(ColorUtil.toArgb((byte) 147, (byte) 124, (byte) 113), (byte) 144); + minecraftColors.put(ColorUtil.toArgb((byte) 180, (byte) 152, (byte) 138), (byte) 145); + minecraftColors.put(ColorUtil.toArgb((byte) 209, (byte) 177, (byte) 161), (byte) 146); + minecraftColors.put(ColorUtil.toArgb((byte) 110, (byte) 93, (byte) 85), (byte) 147); + minecraftColors.put(ColorUtil.toArgb((byte) 112, (byte) 57, (byte) 25), (byte) 148); + minecraftColors.put(ColorUtil.toArgb((byte) 137, (byte) 70, (byte) 31), (byte) 149); + minecraftColors.put(ColorUtil.toArgb((byte) 159, (byte) 82, (byte) 36), (byte) 150); + minecraftColors.put(ColorUtil.toArgb((byte) 84, (byte) 43, (byte) 19), (byte) 151); + minecraftColors.put(ColorUtil.toArgb((byte) 105, (byte) 61, (byte) 76), (byte) 152); + minecraftColors.put(ColorUtil.toArgb((byte) 128, (byte) 75, (byte) 93), (byte) 153); + minecraftColors.put(ColorUtil.toArgb((byte) 149, (byte) 87, (byte) 108), (byte) 154); + minecraftColors.put(ColorUtil.toArgb((byte) 78, (byte) 46, (byte) 57), (byte) 155); + minecraftColors.put(ColorUtil.toArgb((byte) 79, (byte) 76, (byte) 97), (byte) 156); + minecraftColors.put(ColorUtil.toArgb((byte) 96, (byte) 93, (byte) 119), (byte) 157); + minecraftColors.put(ColorUtil.toArgb((byte) 112, (byte) 108, (byte) 138), (byte) 158); + minecraftColors.put(ColorUtil.toArgb((byte) 59, (byte) 57, (byte) 73), (byte) 159); + minecraftColors.put(ColorUtil.toArgb((byte) 131, (byte) 93, (byte) 25), (byte) 160); + minecraftColors.put(ColorUtil.toArgb((byte) 160, (byte) 114, (byte) 31), (byte) 161); + minecraftColors.put(ColorUtil.toArgb((byte) 186, (byte) 133, (byte) 36), (byte) 162); + minecraftColors.put(ColorUtil.toArgb((byte) 98, (byte) 70, (byte) 19), (byte) 163); + minecraftColors.put(ColorUtil.toArgb((byte) 72, (byte) 82, (byte) 37), (byte) 164); + minecraftColors.put(ColorUtil.toArgb((byte) 88, (byte) 100, (byte) 45), (byte) 165); + minecraftColors.put(ColorUtil.toArgb((byte) 103, (byte) 117, (byte) 53), (byte) 166); + minecraftColors.put(ColorUtil.toArgb((byte) 54, (byte) 61, (byte) 28), (byte) 167); + minecraftColors.put(ColorUtil.toArgb((byte) 112, (byte) 54, (byte) 55), (byte) 168); + minecraftColors.put(ColorUtil.toArgb((byte) 138, (byte) 66, (byte) 67), (byte) 169); + minecraftColors.put(ColorUtil.toArgb((byte) 160, (byte) 77, (byte) 78), (byte) 170); + minecraftColors.put(ColorUtil.toArgb((byte) 84, (byte) 40, (byte) 41), (byte) 171); + minecraftColors.put(ColorUtil.toArgb((byte) 40, (byte) 28, (byte) 24), (byte) 172); + minecraftColors.put(ColorUtil.toArgb((byte) 49, (byte) 35, (byte) 30), (byte) 173); + minecraftColors.put(ColorUtil.toArgb((byte) 57, (byte) 41, (byte) 35), (byte) 174); + minecraftColors.put(ColorUtil.toArgb((byte) 30, (byte) 21, (byte) 18), (byte) 175); + minecraftColors.put(ColorUtil.toArgb((byte) 95, (byte) 75, (byte) 69), (byte) 176); + minecraftColors.put(ColorUtil.toArgb((byte) 116, (byte) 92, (byte) 84), (byte) 177); + minecraftColors.put(ColorUtil.toArgb((byte) 135, (byte) 107, (byte) 98), (byte) 178); + minecraftColors.put(ColorUtil.toArgb((byte) 71, (byte) 56, (byte) 51), (byte) 179); + minecraftColors.put(ColorUtil.toArgb((byte) 61, (byte) 64, (byte) 64), (byte) 180); + minecraftColors.put(ColorUtil.toArgb((byte) 75, (byte) 79, (byte) 79), (byte) 181); + minecraftColors.put(ColorUtil.toArgb((byte) 87, (byte) 92, (byte) 92), (byte) 182); + minecraftColors.put(ColorUtil.toArgb((byte) 46, (byte) 48, (byte) 48), (byte) 183); + minecraftColors.put(ColorUtil.toArgb((byte) 86, (byte) 51, (byte) 62), (byte) 184); + minecraftColors.put(ColorUtil.toArgb((byte) 105, (byte) 62, (byte) 75), (byte) 185); + minecraftColors.put(ColorUtil.toArgb((byte) 122, (byte) 73, (byte) 88), (byte) 186); + minecraftColors.put(ColorUtil.toArgb((byte) 64, (byte) 38, (byte) 46), (byte) 187); + minecraftColors.put(ColorUtil.toArgb((byte) 53, (byte) 43, (byte) 64), (byte) 188); + minecraftColors.put(ColorUtil.toArgb((byte) 65, (byte) 53, (byte) 79), (byte) 189); + minecraftColors.put(ColorUtil.toArgb((byte) 76, (byte) 62, (byte) 92), (byte) 190); + minecraftColors.put(ColorUtil.toArgb((byte) 40, (byte) 32, (byte) 48), (byte) 191); + minecraftColors.put(ColorUtil.toArgb((byte) 53, (byte) 35, (byte) 24), (byte) 192); + minecraftColors.put(ColorUtil.toArgb((byte) 65, (byte) 43, (byte) 30), (byte) 193); + minecraftColors.put(ColorUtil.toArgb((byte) 76, (byte) 50, (byte) 35), (byte) 194); + minecraftColors.put(ColorUtil.toArgb((byte) 40, (byte) 26, (byte) 18), (byte) 195); + minecraftColors.put(ColorUtil.toArgb((byte) 53, (byte) 57, (byte) 29), (byte) 196); + minecraftColors.put(ColorUtil.toArgb((byte) 65, (byte) 70, (byte) 36), (byte) 197); + minecraftColors.put(ColorUtil.toArgb((byte) 76, (byte) 82, (byte) 42), (byte) 198); + minecraftColors.put(ColorUtil.toArgb((byte) 40, (byte) 43, (byte) 22), (byte) 199); + minecraftColors.put(ColorUtil.toArgb((byte) 100, (byte) 42, (byte) 32), (byte) 200); + minecraftColors.put(ColorUtil.toArgb((byte) 122, (byte) 51, (byte) 39), (byte) 201); + minecraftColors.put(ColorUtil.toArgb((byte) 142, (byte) 60, (byte) 46), (byte) 202); + minecraftColors.put(ColorUtil.toArgb((byte) 75, (byte) 31, (byte) 24), (byte) 203); + minecraftColors.put(ColorUtil.toArgb((byte) 26, (byte) 15, (byte) 11), (byte) 204); + minecraftColors.put(ColorUtil.toArgb((byte) 31, (byte) 18, (byte) 13), (byte) 205); + minecraftColors.put(ColorUtil.toArgb((byte) 37, (byte) 22, (byte) 16), (byte) 206); + minecraftColors.put(ColorUtil.toArgb((byte) 19, (byte) 11, (byte) 8), (byte) 207); + + MINECRAFT_RGB_COLOR_CODES = Int2ByteMaps.unmodifiable(minecraftColors); + MINECRAFT_RGB_COLORS = MINECRAFT_RGB_COLOR_CODES.keySet(); + // + } + + /** + * Creates a new primitive map + * with keys being an {@code int} representation of an RGB color in Minecraft map + * and value being its code in Minecraft. + * + * @return primitive map of available map color codes in minecraft by their RGB colors + */ + public Int2ByteMap asNewPrimitiveMap() { + return new Int2ByteOpenHashMap(MINECRAFT_RGB_COLOR_CODES); + } + + /////////////////////////////////////////////////////////////////////////// + // Minecraft color conversions + /////////////////////////////////////////////////////////////////////////// + + /** + * Checks whether the specified color can be shown on a map without any distortion. + * + * @param rgb RGB color as {@code int} + * @return {@code true} if this color can be shown on an in-game map without distortion anf {@code false} otherwise + */ + public boolean isMinecraftColor(final int rgb) { + for (val minecraftRgbColor : MINECRAFT_RGB_COLORS) if (rgb == minecraftRgbColor) return true; + return false; + } + + /** + * Checks whether the specified color can be shown on a map without any distortion. + * + * @param red red channel of the RGB color + * @param green green channel of the RGB color + * @param blue blue channel of the RGB color + * @return {@code true} if this color can be shown on an in-game map without distortion anf {@code false} otherwise + */ + public boolean isMinecraftColor(final byte red, final byte green, final byte blue) { + return isMinecraftColor(ColorUtil.toArgb(red, green, blue)); + } + + /** + * Gets Minecraft map color code for the specified color. + * + * @param rgb RGB color as {@code int} + * @return non-zero value being the found minecraft code for the color or {@code 0} if none was found + */ + public byte getMinecraftColorCode(final int rgb) { + return MINECRAFT_RGB_COLOR_CODES.get(rgb); + } + + /** + * Gets Minecraft map color code for the specified color. + * + * @param red red channel of the RGB color + * @param green green channel of the RGB color + * @param blue blue channel of the RGB color + * @return value being the found minecraft code for the color + * or {@link MapImageColor#NO_COLOR_CODE} if none was found + */ + public byte getMinecraftColorCode(final byte red, final byte green, final byte blue) { + return MINECRAFT_RGB_COLOR_CODES.get(ColorUtil.toArgb(red, green, blue)); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImages.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImages.java new file mode 100644 index 000000000..c237bf7cb --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImages.java @@ -0,0 +1,205 @@ +package ru.progrm_jarvis.minecraft.commons.mapimage; + +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import lombok.val; +import lombok.var; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Arrays; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.awt.image.BufferedImage.TYPE_INT_ARGB; +import static java.lang.Math.min; +import static ru.progrm_jarvis.minecraft.commons.mapimage.MapImage.*; +import static ru.progrm_jarvis.minecraft.commons.mapimage.MapImageColor.NO_COLOR_CODE; + +/** + * Utilities related to {@link MapImage}. + * + * @apiNote the standard for 1-dimensional arrays of image pixels is {@code pixel(x, y) = pixels[x + y * width} + */ +@UtilityClass +public class MapImages { + + /** + * Normalizes the RGB-pixels array making them valid Minecraft {@link MapImageColor}s. + * + * @param pixels pixels to normalizes + * @return array of valid Minecraft map color IDs. + */ + protected static byte[] normalizePixels(final int[] pixels) { + // check before allocations + checkArgument(pixels.length == PIXELS_COUNT, "Length of pixels should be " + PIXELS_COUNT); + + val normalizedPixels = new byte[PIXELS_COUNT]; + for (var i = 0; i < pixels.length; i++) normalizedPixels[i] = MapImageColor.getClosestColorCode(pixels[i]); + + return normalizedPixels; + } + + /** + * Normalizes the RGB-pixels 2-dimensional array making them valid Minecraft {@link MapImageColor}s. + * + * @param pixels pixels to normalizes + * @return 2-dimensional array of valid Minecraft map color IDs. + */ + protected static byte[][] normalizePixels(final int[][] pixels) { + val normalizedPixels = new byte[pixels.length][]; + for (var x = 0; x < WIDTH; x++) { + val column = pixels[x]; + val normalizedColumn = new byte[pixels[x].length]; + for (var y = 0; y < HEIGHT; y++) normalizedColumn[y] = MapImageColor.getClosestColorCode(column[y]); + normalizedPixels[x] = normalizedColumn; + } + + return normalizedPixels; + } + + /** + * Makes the image fit the bounds of map image. + *

+ * The logic is the following: + * + *

+ *
The image is {@link MapImage#WIDTH}×{@link MapImage#HEIGHT} or is smaller
+ *
Do nothing and return this image
+ * + *
The image's width is bigger than {@link MapImage#WIDTH} + * or its height is bigger than{@link MapImage#HEIGHT}
+ *
The image is resized proportionally to be of maximal possible size + * yet fitting the bounds of {@link MapImage#WIDTH}×{@link MapImage#HEIGHT}
+ *
+ * + * @param image image to fit (will be redrawn) + * @param resize whether the image should be resized ({@code true}) or cut ({@code false}) + * @return the given image redrawn so that its non-empty pixels + * are in bound of {@link MapImage#WIDTH}×{@link MapImage#HEIGHT} + * + * @apiNote returned object may be ignored as all changes happen to the provided image + */ + @Contract(pure = true) + public @NotNull BufferedImage fitImage(final @NonNull BufferedImage image, final boolean resize) { + int width = image.getWidth(), height = image.getHeight(); + + // if an image is bigger at any bound + if (width > WIDTH || height > HEIGHT) if (resize) { + + // resizing should be proportional, so: + // k(bound) = bound / maxAllowed(bound) + // then divide both by bigger k (treat same optimally!) + + final float kWidth = width / WIDTH_F, kHeight = height / HEIGHT_F; + if (kWidth == kHeight) { + // if both overflow coefficients are the same than the image's proportions are same to image map's + width = WIDTH; + height = HEIGHT; + } else if (kWidth > kHeight) { + // width overflow coefficient is bigger + //noinspection SuspiciousNameCombination the relatively least dimension is diveided by bigger's coef. + height /= kWidth; + width = WIDTH; + } else { + // height overflow coefficient is bigger + //noinspection SuspiciousNameCombination the relatively least dimension is diveided by bigger's coef. + width /= kHeight; + height = HEIGHT; + } + + val newImage = new BufferedImage(width, height, TYPE_INT_ARGB); + val graphics = newImage.getGraphics(); + graphics.drawImage(image.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null); + graphics.dispose(); + + return newImage; + } else { + // cut image + return image.getSubimage(0, 0, min(width, WIDTH), min(height, HEIGHT)); + } + + return image; + } + + /** + * Gets the 2-dimensional {@code byte}-array (the 1-st index is columns, the 2-nd index is rows) + * of size {@link MapImage#WIDTH}×{@link MapImage#HEIGHT} of RGB-{@code int} colors. + * + * @param image image whose pixels to get + * @param resize whether the image should be resized or cut to fit map image dimensions + * @return 2-dimensional array of RGB-{@code int} colors. + */ + public int[][] getNonNormalizedMapImagePixels2D(@NonNull BufferedImage image, final boolean resize) { + image = fitImage(image, resize); + final int width = image.getWidth(), height = image.getHeight(); + + val pixels = new int[WIDTH][HEIGHT]; + val rgb = image.getRGB(0, 0, width, height, new int[width * height], 0, width); + + // whether or not empty pixels should be added as height is not ideal + val requiresEmptyYFilling = width < WIDTH; + + for (var x = 0; x < width; x++) { + val column = pixels[x]; + for (var y = 0; y < height; y++) column[y] = rgb[x + y * width]; + if (requiresEmptyYFilling) Arrays.fill(column, height, HEIGHT, NO_COLOR_CODE); + } + if (width < WIDTH) for (var x = width; x < WIDTH; x++) Arrays.fill(pixels[x], NO_COLOR_CODE); + + return pixels; + } + + /** + * Gets the {@code byte}-array of RGB-{@code int} colors. + * + * @param image image whose pixels to get + * @param resize whether the image should be resized or cut to fit map image dimensions + * @return array of RGB-{@code int} colors. + */ + public int[] getNonNormalizedMapImagePixels(@NonNull BufferedImage image, final boolean resize) { + image = fitImage(image, resize); + final int width = image.getWidth(), height = image.getHeight(); + + val pixels = new int[PIXELS_COUNT]; + val rgb = image.getRGB(0, 0, width, height, new int[width * height], 0, width); + + // whether or not empty pixels should be added as width is not ideal + val requiresEmptyXFilling = width < WIDTH; + + // index in rgb + var i = 0; + for (var y = 0; y < height; y++) { + val rowOffset = y * width; + for (var x = 0; x < width; x++) pixels[x + rowOffset] = rgb[i++]; + if (requiresEmptyXFilling) Arrays.fill(pixels, width + rowOffset, WIDTH + rowOffset, NO_COLOR_CODE); + } + if (height < HEIGHT) Arrays.fill(pixels, height * WIDTH, pixels.length, NO_COLOR_CODE); + + return pixels; + } + + /** + * Gets the 2-dimensional {@code byte}-array (the 1-st index is columns, the 2-nd index is rows) + * of size {@link MapImage#WIDTH}×{@link MapImage#HEIGHT} of valid map color ids. + * + * @param image image whose pixels to get + * @param resize whether the image should be resized or cut to fit map image dimensions + * @return 2-dimensional array of {@link MapImageColor} IDs valid for minecraft. + */ + public byte[][] getMapImagePixels2D(final @NonNull BufferedImage image, final boolean resize) { + return normalizePixels(getNonNormalizedMapImagePixels2D(image, resize)); + } + + /** + * Gets the {@code byte}-array of size {@link MapImage#PIXELS_COUNT} of valid map color ids. + * + * @param image image whose pixels to get + * @param resize whether the image should be resized or cut to fit map image dimensions + * @return array of {@link MapImageColor} IDs valid for minecraft. + */ + public byte[] getMapImagePixels(final @NonNull BufferedImage image, final boolean resize) { + return normalizePixels(getNonNormalizedMapImagePixels(image, resize)); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/display/BlankMapRenderer.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/display/BlankMapRenderer.java new file mode 100644 index 000000000..8e4c504b2 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/display/BlankMapRenderer.java @@ -0,0 +1,24 @@ +package ru.progrm_jarvis.minecraft.commons.mapimage.display; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.bukkit.entity.Player; +import org.bukkit.map.MapCanvas; +import org.bukkit.map.MapRenderer; +import org.bukkit.map.MapView; + +@ToString +@EqualsAndHashCode(callSuper = true) +public class BlankMapRenderer extends MapRenderer { + + public static final BlankMapRenderer + CONTEXTUAL = new BlankMapRenderer(true), + NON_CONTEXTUAL = new BlankMapRenderer(false); + + public BlankMapRenderer(final boolean contextual) { + super(contextual); + } + + @Override + public void render(final MapView map, final MapCanvas canvas, final Player player) {} +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/display/MapImageDisplay.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/display/MapImageDisplay.java new file mode 100644 index 000000000..674ca5e7e --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/display/MapImageDisplay.java @@ -0,0 +1,31 @@ +package ru.progrm_jarvis.minecraft.commons.mapimage.display; + +import lombok.NonNull; +import org.bukkit.entity.Player; +import ru.progrm_jarvis.minecraft.commons.mapimage.MapImage; +import ru.progrm_jarvis.minecraft.commons.player.collection.PlayerContainer; + +import java.util.Optional; + +/** + * Display of {@link MapImage}. + * It is responsible for displaying actual images to the players and resolving map-ID conflicts. + */ +public interface MapImageDisplay extends PlayerContainer { + + /** + * Gets the image displayed. + * + * @return image displayed + */ + MapImage image(); + + /** + * Gets the map ID used by this map image display for the player specified. + * + * @param player player whose map ID for this image display to get + * @return optional containing map ID used by this map image display for the player + * or empty optional if none (this display is not used for the player) + */ + @NonNull Optional getMapId(@NonNull Player player); +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/display/PlayerMapManager.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/display/PlayerMapManager.java new file mode 100644 index 000000000..36b87a9ad --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/display/PlayerMapManager.java @@ -0,0 +1,269 @@ +package ru.progrm_jarvis.minecraft.commons.mapimage.display; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; +import lombok.NonNull; +import lombok.SneakyThrows; +import lombok.Synchronized; +import lombok.experimental.UtilityClass; +import lombok.val; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.map.MapView; +import ru.progrm_jarvis.javacommons.invoke.InvokeUtil; +import ru.progrm_jarvis.minecraft.commons.MinecraftCommons; +import ru.progrm_jarvis.minecraft.commons.util.SystemPropertyUtil; + +import java.io.File; +import java.io.IOException; +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Utility responsible to allocate minimal amount of {@link MapView} for internal usage. + *

+ * This allocates IDs once needed and reuses them as much as possible, so that: + *

    + *
  • Two players can see different images by one same ID at the same time
  • + *
  • Any player can see different images by single ID at different time
  • + *
  • IDs are stored and reused at server restarts so that no useless IDs allocations occur
  • + *
+ *

+ * This requires the developer to free maps using {@link #freeMap(Player, MapView)} + * (obtained from {@link #allocateMap(Player)}) once those are no longer required in order to use those optimally. + */ +@UtilityClass +public class PlayerMapManager { + + /** + * Flag describing whether {@code int} or {@code short} map IDs are used by current server. + */ + private final boolean USE_INT_IDS; + + /** + * Method handle for {@link MapView#getId()} because it returns + * {@code short} and {@code int} on different Bukkit API versions. + */ + private final MethodHandle MAP_VIEW__GET_ID__METHOD, + /** + * Method handle for {@link Bukkit#getMap(int)} because it consumes + * {@code short} and {@code int} on different Bukkit API versions. + */ + BUKKIT__GET_MAP__METHOD; + + static { + { + final Method method; + try { + method = MapView.class.getDeclaredMethod("getId"); + } catch (final NoSuchMethodException e) { + throw new IllegalStateException("Cannot find method " + MapView.class.getCanonicalName() + "#getId()"); + } + val returnType = method.getReturnType(); + if (returnType == int.class) USE_INT_IDS = true; + else if (returnType == short.class) USE_INT_IDS = false; + else throw new IllegalStateException("Unknown return type of MapView#getId() method (" + returnType + ")"); + + MAP_VIEW__GET_ID__METHOD = InvokeUtil.toMethodHandle(method); + } + try { + BUKKIT__GET_MAP__METHOD = InvokeUtil.toMethodHandle( + Bukkit.class.getDeclaredMethod("getMap", USE_INT_IDS ? int.class : short.class) + ); + } catch (final NoSuchMethodException e) { + throw new IllegalStateException("Cannot find method " + Bukkit.class.getCanonicalName() + + "#getMap(" + (USE_INT_IDS ? "int" : "short") + ")"); + } + } + + /** + * Root directory of {@link PlayerMapManager} in which internal data is stored between sessions. + */ + private final File ROOT_DIRECTORY = new File(MinecraftCommons.ROOT_DIRECTORY, + SystemPropertyUtil.getSystemProperty( + PlayerMapManager.class.getCanonicalName() + ".internal-ids-list-file-name", Function.identity(), + "player_map_id_manager/" + ) + ); + + /** + * File containing IDs of allocated maps. + */ + private final File IDS_LIST_FILE = new File(ROOT_DIRECTORY, "map_ids.list"); + + /** + * Maps allocated in Bukkit for internal usage. + */ + private final Set ALLOCATED_MAPS; + + /** + * Maps of {@link #ALLOCATED_MAPS} available for the player. + * Similar maps may be related to different players as the image logic doesn't intersect. + */ + private final SetMultimap PLAYER_MAPS; + + // static initialization of file-related stuff + static { + if (!ROOT_DIRECTORY.isFile()) try { + Files.createDirectories(ROOT_DIRECTORY.toPath()); + } catch (final IOException e) { + throw new RuntimeException("Couldn't create source directory of PlayerMapManager", e); + } + + // Loads the maps stored in a file between sessions + if (IDS_LIST_FILE.isFile()) { + final List fileLines; + try { + fileLines = Files.readAllLines(IDS_LIST_FILE.toPath()); + } catch (final IOException e) { + throw new RuntimeException( + "Couldn't read file " + IDS_LIST_FILE.getName() + " of PlayerMapManager ", e + ); + } + + ALLOCATED_MAPS = fileLines.stream() + .filter(line -> !line.isEmpty() && line.indexOf('#') != 0) + .map(line -> getMap(Integer.parseInt(line))) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); // HashSet is used by default + + Bukkit.getLogger().info( + "Loaded " + ALLOCATED_MAPS.size() + " internally allocated world map IDs: " + ALLOCATED_MAPS + ); + } else { + try { + Files.createFile(IDS_LIST_FILE.toPath()); + } catch (final IOException e) { + throw new RuntimeException( + "Couldn't create file " + IDS_LIST_FILE.getName() + " of PlayerMapManager ", e + ); + } + ALLOCATED_MAPS = new HashSet<>(); + } + + // use optimal key size for allocatedMap + PLAYER_MAPS = HashMultimap.create( + Math.max(8, ALLOCATED_MAPS.size()), Math.max(16, Bukkit.getOnlinePlayers().size()) + ); + } + + /** + * Gets {@link MapView}'s ID independently on version. + * + * @param mapView {@link MapView} whose {@link MapView#getId()} to invoke + * @return map view's ID + */ + @SneakyThrows + public int getMapId(final @NonNull MapView mapView) { + if (USE_INT_IDS) return (int) MAP_VIEW__GET_ID__METHOD.invokeExact(mapView); + return (int) (short) MAP_VIEW__GET_ID__METHOD.invokeExact(mapView); + } + + /** + * Gets the {@link MapView} from the given ID independently on version. + * + * @param mapId id of the map to get + * @return a map view if it exists, or null otherwise + */ + @SneakyThrows + public MapView getMap(final int mapId) { + return USE_INT_IDS // note: same casts at both places to be able to use `invokeExact` + ? (MapView) BUKKIT__GET_MAP__METHOD.invokeExact(mapId) + : (MapView) BUKKIT__GET_MAP__METHOD.invokeExact((short) mapId); + } + + /** + * Allocates new map instance. + * + * @return newly allocated map with only blank renderer applied + * + * @apiNote should be called only in case of need + */ + @Synchronized + private MapView allocateNewMap() { + final MapView map; + { + val worlds = Bukkit.getWorlds(); + if (worlds.isEmpty()) throw new IllegalStateException("There are no Bukkit worlds available"); + map = Bukkit.createMap(worlds.get(0)); + } + + try { + Files.write( + IDS_LIST_FILE.toPath(), + (getMapId(map) + System.lineSeparator()).getBytes(), + StandardOpenOption.APPEND + ); + } catch (final IOException e) { + throw new RuntimeException( + "Couldn't write newly allocated ID to " + IDS_LIST_FILE.getName() + " of PlayerMapManager", e + ); + } + + // clear renderers for map + for (val renderer : map.getRenderers()) map.removeRenderer(renderer); + ALLOCATED_MAPS.add(map); + + return map; + } + + /** + * Allocates new map view for player. + * This returns a map view which is stored in this manager and is free for the player at the moment. + * The map should be freed for the player once he can't see it / he leaves the server. + * + * @param player player for whom to allocate the map + * @return map allocated for the player + * + * @see #freeMap(Player, MapView) should be called whenever the player stops seeing this map or leaves the server + */ + @Synchronized + public MapView allocateMap(final @NonNull Player player) { + val mapsOfPlayer = PLAYER_MAPS.get(player); + + // if the player has all maps allocated of available than allocate another one (specially for him <3) + // this is a fast equivalent of iterating through each allocated map + // because only maps from allocatedMaps may be contained in mapsOfPlayers which is a Set + if (mapsOfPlayer.size() == ALLOCATED_MAPS.size()) { + val map = allocateNewMap(); + mapsOfPlayer.add(map); + + return map; + } + //otherwise take one of the available maps for him + for (val map : ALLOCATED_MAPS) if (!mapsOfPlayer.contains(map)) { + mapsOfPlayer.add(map); + + return map; + } + + // this should never be reached + throw new IllegalStateException( + "Something went wrong, could not find any allocated map for the player although there should be one" + ); + } + + /** + * Frees the map for the player so that it can be reused. + * This should be called whenever the player stops seeing this map or leaves the server. + * + * @param player player for whom to free the map + * @param map map which should be freed + * @apiNote this must be called for any map allocation once it can be free + * + * @see #allocateMap(Player) only obtained by calling this method should be freed + */ + @Synchronized + public void freeMap(final @NonNull Player player, final @NonNull MapView map) { + PLAYER_MAPS.remove(player, map); + for (val renderer : map.getRenderers()) map.removeRenderer(renderer); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/display/ProtocolBasedMapImageDisplay.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/display/ProtocolBasedMapImageDisplay.java new file mode 100644 index 000000000..dde2660ff --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mapimage/display/ProtocolBasedMapImageDisplay.java @@ -0,0 +1,136 @@ +package ru.progrm_jarvis.minecraft.commons.mapimage.display; + +import com.comphenix.packetwrapper.WrapperPlayServerMap; +import lombok.*; +import lombok.experimental.FieldDefaults; +import org.bukkit.entity.Player; +import org.bukkit.map.MapView; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.Nullable; +import ru.progrm_jarvis.javacommons.collection.MapUtil; +import ru.progrm_jarvis.minecraft.commons.mapimage.MapImage; +import ru.progrm_jarvis.minecraft.commons.player.registry.PlayerRegistries; +import ru.progrm_jarvis.minecraft.commons.player.registry.PlayerRegistry; +import ru.progrm_jarvis.minecraft.commons.player.registry.PlayerRegistryRegistration; + +import java.util.*; + +@ToString +@EqualsAndHashCode +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +public class ProtocolBasedMapImageDisplay implements MapImageDisplay { + + @NonNull MapImage image; + @NonNull Map playerMaps; + @NonNull Set playersView; + @Getter boolean global; + + @PlayerRegistryRegistration(PlayerRegistryRegistration.Policy.AUTO) + public ProtocolBasedMapImageDisplay(final @NonNull MapImage image, final @NonNull Map playerMaps, + final @NonNull Plugin plugin, final boolean global, + final @NonNull PlayerRegistry playerRegistry) { + this.image = image; + this.playerMaps = playerMaps; + playersView = Collections.unmodifiableSet(playerMaps.keySet()); + this.global = global; + + playerRegistry.register(this); + image.subscribeOnUpdates(this::sendDeltaToAllPlayers); + } + + @PlayerRegistryRegistration(PlayerRegistryRegistration.Policy.AUTO) + public ProtocolBasedMapImageDisplay(final @NonNull MapImage image, final @NonNull Map playerMaps, + final @NonNull Plugin plugin, final boolean global) { + this(image, playerMaps, plugin, global, PlayerRegistries.defaultRegistry(plugin)); + } + + @Override + public MapImage image() { + return image; + } + + /** + * Sends the whole image to the players. + * + * @param player player to whom the image should be sent + */ + protected void sendFullImage(final @NonNull Player player) { + new WrapperPlayServerMap() {{ + setItemDamage(PlayerMapManager.getMapId(playerMaps.get(player))); + setScale(image.getDisplay()); + setColumns(image.getWidth()); + setRows(image.getHeight()); + setX(0); + setZ(0); + setData(image.getMapData()); + }}.sendPacket(player); + } + + protected WrapperPlayServerMap newDeltaPacket(final @Nullable MapImage.Delta delta) { + return new WrapperPlayServerMap() {{ + setScale(image.getDisplay()); + setColumns(delta.width()); + setRows(delta.height()); + setX(delta.leastX()); + setZ(delta.leastY()); + setData(image.getMapData(delta)); + }}; + } + + protected void sendDelta(final @NonNull Player player, final @NonNull MapImage.Delta delta) { + if (delta.isEmpty()) return; + + val packet = newDeltaPacket(delta); + packet.setItemDamage(PlayerMapManager.getMapId(playerMaps.get(player))); + + packet.sendPacket(player); + } + + protected void sendDeltaToAllPlayers(final @NonNull MapImage.Delta delta) { + if (delta.isEmpty()) return; + + val packet = newDeltaPacket(delta); + + for (val entry : playerMaps.entrySet()) { + packet.setItemDamage(PlayerMapManager.getMapId(entry.getValue())); + packet.sendPacket(entry.getKey()); + } + } + + @Override + public void addPlayer(final Player player) { + // computeIfAbsent not to allocate the ID if the player is already contained (and so has ID allocated) + playerMaps.computeIfAbsent(player, p -> { + val map = PlayerMapManager.allocateMap(player); + // although the rendering is contextual there is no need to use Bukkit's contextual renderer + map.addRenderer(BlankMapRenderer.NON_CONTEXTUAL); + + return map; + }); + sendFullImage(player); + } + + @Override + public void removePlayer(final Player player) { + val map = playerMaps.remove(player); + if (map != null) /* null-check just in case */ PlayerMapManager.freeMap(player, map); + } + + @Override + public boolean containsPlayer(final Player player) { + return playerMaps.containsKey(player); + } + + @Override + public Collection getPlayers() { + return playersView; + } + + @Override + @NonNull + public Optional getMapId(final @NonNull Player player) { + return MapUtil.>getOrDefault( + playerMaps, player, map -> Optional.of(PlayerMapManager.getMapId(map)), Optional::empty + ); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/math/dimensional/CuboidFigure.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/math/dimensional/CuboidFigure.java new file mode 100644 index 000000000..bb50b92fe --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/math/dimensional/CuboidFigure.java @@ -0,0 +1,95 @@ +package ru.progrm_jarvis.minecraft.commons.math.dimensional; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import lombok.experimental.NonFinal; +import org.bukkit.Location; +import org.bukkit.util.Vector; + +/** + * A cuboid figure. + */ +@Value +@FieldDefaults(level = AccessLevel.PROTECTED) +@NonFinal public class CuboidFigure implements Figure3D { + + @Getter double minX, minY, minZ, maxX, maxY, maxZ; + + public CuboidFigure(final double x1, final double y1, final double z1, + final double x2, final double y2, final double z2) { + if (x1 < x2) { + minX = x1; + maxX = x2; + } else { + minX = x2; + maxX = x1; + } + + if (y1 < y2) { + minY = y1; + maxY = y2; + } else { + minY = y2; + maxY = y1; + } + + if (z1 < z2) { + minZ = z1; + maxZ = z2; + } else { + minZ = z2; + maxZ = z1; + } + } + + public CuboidFigure(final double x, final double y, final double z) { + this(0, 0, 0, x, y, z); + } + + @Override + public boolean contains(final double x, final double y, final double z) { + return minX <= x && x <= maxX + && minY <= y && y <= maxY + && minZ <= z && z <= maxZ; + } + + /** + * Creates a cubic figure based on two points. + * + * @param x1 X-coordinate of the first point of the cubic figure + * @param y1 T-coordinate of the first point of the cubic figure + * @param z1 Z-coordinate of the first point of the cubic figure + * @param x2 X-coordinate of the second point of the cubic figure + * @param y2 T-coordinate of the second point of the cubic figure + * @param z2 Z-coordinate of the second point of the cubic figure + * @return created cubic figure including the area between the two points + */ + @NonNull + private static Figure3D between(final double x1, final double y1, final double z1, + final double x2, final double y2, final double z2) { + if (x1 == x2 && y1 == y2 && z1 == z2) return new PointFigure(x1, y1, z1); + return new CuboidFigure(x1, y1, z1, x2, y2, z2); + } + + /** + * Creates a cubic figure based on two points. + * + * @param point1 the first point of the cubic figure + * @param point2 the second point of the cubic figure + * @return created cubic figure including the area between the two points + */ + public static Figure3D between(final @NonNull Vector point1, final @NonNull Vector point2) { + return between(point1.getX(), point1.getY(), point1.getZ(), point2.getX(), point2.getY(), point2.getZ()); + } + + /** + * Creates a cubic figure based on two points. + * + * @param point1 the first point of the cubic figure + * @param point2 the second point of the cubic figure + * @return created cubic figure including the area between the two points + */ + public static Figure3D between(final @NonNull Location point1, final @NonNull Location point2) { + return between(point1.getX(), point1.getY(), point1.getZ(), point2.getX(), point2.getY(), point2.getZ()); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/math/dimensional/Figure3D.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/math/dimensional/Figure3D.java new file mode 100644 index 000000000..e813eb39c --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/math/dimensional/Figure3D.java @@ -0,0 +1,192 @@ +package ru.progrm_jarvis.minecraft.commons.math.dimensional; + +import lombok.NonNull; +import org.bukkit.Location; +import org.bukkit.util.Vector; + +/** + * A figure existing in 3-dimensional system. + */ +public interface Figure3D { + + /** + * A singleton pseudo-figure not containing any possible points of a 3D-space. + */ + Figure3D EMPTY = new Figure3D() { + // + + @Override + public double getMinX() { + return Double.NaN; + } + + @Override + public double getMaxX() { + return Double.NaN; + } + + @Override + public double getMinY() { + return Double.NaN; + } + + @Override + public double getMaxY() { + return Double.NaN; + } + + @Override + public double getMinZ() { + return Double.NaN; + } + + @Override + public double getMaxZ() { + return Double.NaN; + } + + @Override + public boolean contains(final double x, final double y, final double z) { + return false; + } + + @Override + public boolean contains(final @NonNull Vector point) { + return false; + } + + @Override + public boolean contains(final @NonNull Location point) { + return false; + } + // + }; + + /** + * A singleton pseudo-figure containing all possible points of a 3D-space. + */ + Figure3D WHOLE = new Figure3D() { + // + + @Override + public double getMinX() { + return Double.NEGATIVE_INFINITY; + } + + @Override + public double getMaxX() { + return Double.POSITIVE_INFINITY; + } + + @Override + public double getMinY() { + return Double.NEGATIVE_INFINITY; + } + + @Override + public double getMaxY() { + return Double.POSITIVE_INFINITY; + } + + @Override + public double getMinZ() { + return Double.NEGATIVE_INFINITY; + } + + @Override + public double getMaxZ() { + return Double.POSITIVE_INFINITY; + } + + @Override + public boolean contains(final double x, final double y, final double z) { + return true; + } + + @Override + public boolean contains(final @NonNull Vector point) { + return true; + } + + @Override + public boolean contains(final @NonNull Location point) { + return true; + } + // + }; + + /** + * Gets the minimal figure's coordinate by X axis. + * + * @return the least X-coordinate of the figure + */ + double getMinX(); + + /** + * Gets the maximal figure's coordinate by X axis. + * + * @return the most X-coordinate of the figure + */ + double getMaxX(); + + /** + * Gets the minimal figure's coordinate by Y axis. + * + * @return the least Y-coordinate of the figure + */ + double getMinY(); + + /** + * Gets the maximal figure's coordinate by Y axis. + * + * @return the most Y-coordinate of the figure + */ + double getMaxY(); + + /** + * Gets the minimal figure's coordinate by Z axis. + * + * @return the least Z-coordinate of the figure + */ + double getMinZ(); + + /** + * Gets the maximal figure's coordinate by Z axis. + * + * @return the most Z-coordinate of the figure + */ + double getMaxZ(); + + /** + * Checks whether or not this figure contains the point. + * + * @param x X coordinate of a point to check + * @param y Y coordinate of a point to check + * @param z Z coordinate of a point to check + * + * @return {@code true} if the point belongs to this figure and {@code false} otherwise + */ + boolean contains(double x, double y, double z); + + /** + * Checks whether or not this figure contains the point. + * + * @param point point to check + * + * @return {@code true} if the point belongs to this figure and {@code false} otherwise + */ + default boolean contains(final @NonNull Vector point) { + return contains(point.getX(), point.getY(), point.getZ()); + } + + /** + * Checks whether or not this figure contains the point. + * + * @param point point to check + * + * @return {@code true} if the point belongs to this figure and {@code false} otherwise + */ + default boolean contains(final @NonNull Location point) { + return contains(point.getX(), point.getY(), point.getZ()); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/math/dimensional/PointFigure.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/math/dimensional/PointFigure.java new file mode 100644 index 000000000..b05214f01 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/math/dimensional/PointFigure.java @@ -0,0 +1,76 @@ +package ru.progrm_jarvis.minecraft.commons.math.dimensional; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.Value; +import lombok.experimental.FieldDefaults; +import lombok.experimental.NonFinal; +import org.bukkit.Location; +import org.bukkit.util.Vector; + +/** + * A point figure covering only one exact coordinate. + */ +@Value +@FieldDefaults(level = AccessLevel.PROTECTED) +@NonFinal public class PointFigure implements Figure3D { + + double x, y, z; + + // + @Override + public double getMinX() { + return x; + } + + @Override + public double getMaxX() { + return x; + } + + @Override + public double getMinY() { + return y; + } + + @Override + public double getMaxY() { + return y; + } + + @Override + public double getMinZ() { + return z; + } + + @Override + public double getMaxZ() { + return z; + } + // + + @Override + public boolean contains(final double x, final double y, final double z) { + return x == this.x && y == this.y && z == this.z; + } + + /** + * Converts the {@link Vector} representation to a {@link PointFigure} representation. + * + * @param point vector representation to convert + * @return point figure identical to the specified vector point + */ + public static PointFigure from(final @NonNull Vector point) { + return new PointFigure(point.getX(), point.getY(), point.getZ()); + } + + /** + * Converts the {@link Location} representation to a {@link PointFigure} representation. + * + * @param point location representation to convert + * @return point figure identical to the specified location point + */ + public static PointFigure from(final @NonNull Location point) { + return new PointFigure(point.getX(), point.getY(), point.getZ()); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mojang/MojangApiManager.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mojang/MojangApiManager.java new file mode 100644 index 000000000..56245246b --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mojang/MojangApiManager.java @@ -0,0 +1,241 @@ +package ru.progrm_jarvis.minecraft.commons.mojang; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import lombok.*; +import lombok.Builder.Default; +import lombok.experimental.FieldDefaults; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.progrm_jarvis.javacommons.lazy.Lazy; +import ru.progrm_jarvis.minecraft.commons.annotation.AsyncExpected; +import ru.progrm_jarvis.minecraft.commons.async.AsyncRunner; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +@ToString +@EqualsAndHashCode +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +public class MojangApiManager implements AutoCloseable { + + /////////////////////////////////////////////////////////////////////////// + // URIs + /////////////////////////////////////////////////////////////////////////// + + public static final String USERNAME_TO_UUID_AT_TIME_URI_PREFIX + = "https://api.mojang.com/users/profiles/minecraft/"; + + public static final String UUID_TO_PROFILE_URI_PREFIX + = "https://sessionserver.mojang.com/session/minecraft/profile/"; + + protected static final JsonParser jsonParser = new JsonParser(); + + Lazy httpConnectionManager; + Lazy httpClient; + + Lazy asyncRunner; + + Lazy> uuidsCache; + Lazy> profilesCache; + + public MojangApiManager(final Configuration configuration) { + httpConnectionManager = Lazy.createThreadSafe(configuration.httpConnectionManager); + { + val httpClientFunction = configuration.httpClient; + httpClient = Lazy.createThreadSafe(() -> httpClientFunction.apply(httpConnectionManager.get())); + } + + asyncRunner = Lazy.createThreadSafe(configuration.asyncRunner); + + uuidsCache = Lazy.createThreadSafe(configuration.uuidsCache); + profilesCache = Lazy.createThreadSafe(configuration.profilesCache); + } + + @Override + public void close() { + if (httpConnectionManager.isInitialized()) httpConnectionManager.get().shutdown(); + } + + /////////////////////////////////////////////////////////////////////////// + // Inner utilities + /////////////////////////////////////////////////////////////////////////// + + @AsyncExpected + protected static JsonElement readJson(final @NonNull InputStream inputStream) { + return jsonParser.parse(new BufferedReader(new InputStreamReader(inputStream))); + } + + @AsyncExpected + protected JsonElement httpGetJson(final @NonNull String uri) throws IOException { + return readJson(httpClient.get().execute(new HttpGet(uri)).getEntity().getContent()); + } + + @AsyncExpected + protected JsonElement httpGetJson(final @NonNull String uri, final @Nullable Map parameters) + throws IOException { + if (parameters == null || parameters.isEmpty()) return httpGetJson(uri); + + val uriBuilder = new StringBuilder(uri) + .append('?'); + + val params = parameters.entrySet(); + val size = parameters.size(); + var i = 1; + for (val param : params) { + // append parameter + uriBuilder + .append(param.getKey()) + .append('=') + .append(param.getValue()); + // if not last param then append '&' + if (i != size) { + uriBuilder.append('&'); + i++; // micro-optimization: increment only if needed + } + } + + return httpGetJson(uriBuilder.toString()); + } + + /////////////////////////////////////////////////////////////////////////// + // Username --> UUID + /////////////////////////////////////////////////////////////////////////// + + @AsyncExpected + public @NotNull UUID readUuid(final @NonNull String userName) throws IOException { + return MojangUtil.fromMojangUuid(httpGetJson(USERNAME_TO_UUID_AT_TIME_URI_PREFIX + userName).getAsJsonObject() + .get("id").getAsString() + ); + } + + public void readUuid(final @NonNull String userName, + final @NonNull Consumer callback) { + asyncRunner.get().runAsynchronously(() -> readUuidUnchecked(userName), callback); + } + + @AsyncExpected + @SneakyThrows(IOException.class) + public @NotNull UUID readUuidUnchecked(final @NonNull String userName) { + return readUuid(userName); + } + + @SneakyThrows + @AsyncExpected + public @NotNull UUID getUuid(final @NonNull String userName) { + return uuidsCache.get() + .get(userName.toLowerCase(), () -> readUuidUnchecked(userName)); + } + + public void getUuid(final @NonNull String userName, final @NonNull Consumer callback) { + asyncRunner.get().runAsynchronously(() -> getUuid(userName), callback); + } + + /////////////////////////////////////////////////////////////////////////// + // UUID --> GameProfile + /////////////////////////////////////////////////////////////////////////// + + @AsyncExpected + public @NotNull GameProfile readProfile(final @NonNull UUID uuid, final boolean signed) throws IOException { + val data = httpGetJson(UUID_TO_PROFILE_URI_PREFIX + MojangUtil.toMojangUuid(uuid) + "?unsigned=" + !signed) + .getAsJsonObject(); + + val profile = new GameProfile(uuid, data.get("name").getAsString()); + + val profileProperties = profile.getProperties(); + + val properties = data.getAsJsonArray("properties"); + for (val property : properties) { + val jsonProperty = property.getAsJsonObject(); + val propertyName = jsonProperty.get("name").getAsString(); + + profileProperties.put(propertyName, new Property( + propertyName, + jsonProperty.get("value").getAsString(), + signed ? jsonProperty.get("signature").getAsString() : null + )); + } + + return profile; + } + + public void readProfile(final @NonNull UUID uuid, final boolean signed, + final @NonNull Consumer callback) { + asyncRunner.get().runAsynchronously(() -> readProfileUnchecked(uuid, signed), callback); + } + + @AsyncExpected + @SneakyThrows(IOException.class) + public @NotNull GameProfile readProfileUnchecked(final @NonNull UUID uuid, final boolean signed) { + return readProfile(uuid, signed); + } + + @SneakyThrows + @AsyncExpected + public @NotNull GameProfile getProfile(final @NonNull UUID uuid, final boolean signed) { + val cache = profilesCache.get(); + var cachedProfile = cache.getIfPresent(uuid); + // get profile using Mojang API if there is no cached one or it is unsigned but has to be + if (cachedProfile == null || (signed && !MojangUtil.isSigned(cachedProfile))) cache + .put(uuid, cachedProfile = readProfile(uuid, signed)); + + return cachedProfile; + } + + public void getProfile(final @NonNull UUID uuid, final boolean signed, + final @NonNull Consumer callback) { + asyncRunner.get().runAsynchronously(() -> getProfile(uuid, signed), callback); + } + + /////////////////////////////////////////////////////////////////////////// + // Configuration class + /////////////////////////////////////////////////////////////////////////// + + @Builder + @FieldDefaults(level = AccessLevel.PROTECTED) + public static class Configuration { + + @Default Supplier httpConnectionManager + = PoolingHttpClientConnectionManager::new; + + @Default Function httpClient = manager -> HttpClients + .custom() + .setConnectionManager(manager) + .build(); + + @Default Supplier> uuidsCache + = () -> CacheBuilder.newBuilder() + .expireAfterWrite(5, TimeUnit.MINUTES) + .build(); + + @Default Supplier> profilesCache + = () -> CacheBuilder.newBuilder() + .expireAfterWrite(5, TimeUnit.SECONDS) + .build(); + + @Default Supplier asyncRunner = () -> { + throw new IllegalStateException("Async tasks are not available in this MojangApiManager"); + }; + + public static Configuration getDefault() { + return builder().build(); + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mojang/MojangUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mojang/MojangUtil.java new file mode 100644 index 000000000..824846355 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/mojang/MojangUtil.java @@ -0,0 +1,58 @@ +package ru.progrm_jarvis.minecraft.commons.mojang; + +import com.mojang.authlib.GameProfile; +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import lombok.val; + +import java.util.UUID; + +/** + * A pack of Utilities related to Mojang API specific stuff. + */ +@UtilityClass +public class MojangUtil { + + /** + * Converts a UUID in a form returned by Mojang API calls (no dashes) to a {@link UUID} object. + * + * @param mojangUuid UUID in a non-standard form returned by Mojang API call (no dashes) to convert + * @return standard UUID object, result of conversion + */ + public UUID fromMojangUuid(final @NonNull String mojangUuid) { + return UUID.fromString( + mojangUuid.substring(0, 8) + + '-' + mojangUuid.substring(8, 12) + + '-' + mojangUuid.substring(12, 16) + + '-' + mojangUuid.substring(16, 20) + + '-' + mojangUuid.substring(20, 32) + ); + } + + /** + * Converts a {@link UUID} object to a form used by Mojang API (no dashes). + * + * @param uuid standard UUID object to convert + * @return UUID in a non-standard form used by Mojang API (no dashes), result of conversion + */ + public String toMojangUuid(final @NonNull UUID uuid) { + val stringUuid = uuid.toString(); + return stringUuid.substring(0, 8) + + stringUuid.substring(9, 13) + + stringUuid.substring(14, 18) + + stringUuid.substring(19, 23) + + stringUuid.substring(24, 36); + } + + /** + * Checks whether the specified profile has all properties signed. + * + * @param profile profile to check + * @return {@code true} if the specified profile has all its properties signed + * or {@code false} if any of those is unsigned + */ + public boolean isSigned(final @NonNull GameProfile profile) { + for (val entry : profile.getProperties().entries()) if (!entry.getValue().hasSignature()) return false; + return true; + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/LegacySupport.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/LegacySupport.java new file mode 100644 index 000000000..cd3c6aa87 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/LegacySupport.java @@ -0,0 +1,108 @@ +package ru.progrm_jarvis.minecraft.commons.nms; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Value; +import lombok.experimental.FieldDefaults; +import lombok.experimental.UtilityClass; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.FallingBlock; +import org.bukkit.material.MaterialData; +import ru.progrm_jarvis.javacommons.collection.MapFiller; + +import java.util.HashMap; +import java.util.Map; + +/** + * Various utilities used for supporting older versions of Minecraft-related environment, + * such as changed enum names and updated method signatures. + */ +@UtilityClass +@SuppressWarnings("deprecation") +public class LegacySupport { + + private static final int NMS_VERSION_GENERATION = NmsUtil.getVersion().getGeneration(); + + private static final boolean LEGACY_MATERIALS = NMS_VERSION_GENERATION < 13; + private static final boolean LEGACY_BLOCK_FALLING = NMS_VERSION_GENERATION < 13; + + private static final Map legacyItems = MapFiller.from(new HashMap()) + // TODO or find better solution .put("CAVE_AIR", new LegacyItem("CAVE_AIR", "AIR")) + // .put("VOID_AIR", new LegacyItem("VOID_AIR", "AIR")) + .map(); + + /** + * Spawns a falling block at specified location. + * + * @param location location at which to spawn the block + * @param block block from which to create the falling one + * @return spawned falling block + */ + public FallingBlock spawnFallingBlock(final @NonNull Location location, final @NonNull Block block) { + if (LEGACY_BLOCK_FALLING) return location.getWorld() + .spawnFallingBlock(location, block.getType(), block.getData()); + return location.getWorld().spawnFallingBlock(location, block.getBlockData()); + } + + /** + * Spawns a falling block at specified location. + * + * @param location location at which to spawn the block + * @param material material of the block + * @param materialData legacy material data + * @return spawned falling block + */ + public FallingBlock spawnFallingBlock(final @NonNull Location location, + final @NonNull Material material, final byte materialData) { + if (LEGACY_BLOCK_FALLING) return location.getWorld() + .spawnFallingBlock(location, material, materialData); + return location.getWorld().spawnFallingBlock(location, new MaterialData(material, materialData)); + } + + private static LegacyItem legacyItem(final @NonNull Material material, final int legacyData) { + return new LegacyItem(material, (byte) legacyData); + } + + private static LegacyItem legacyItem(final @NonNull Material material) { + return legacyItem(material, 0); + } + + private static LegacyItem legacyItem(final @NonNull String materialName, + final @NonNull String legacyMaterialName, + final int legacyData) { + return new LegacyItem(Material.valueOf(LEGACY_MATERIALS ? legacyMaterialName : materialName), legacyData); + } + + private static LegacyItem legacyItem(final @NonNull String materialName, + final @NonNull String legacyMaterialName) { + return legacyItem(materialName, legacyMaterialName, 0); + } + + @Value + @RequiredArgsConstructor + @FieldDefaults(level = AccessLevel.PRIVATE) + private static final class LegacyItem { + @NonNull Material material; + final byte legacyData; + + private LegacyItem(final @NonNull Material material, final int legacyData) { + this(material, (byte) legacyData); + } + + private LegacyItem(final @NonNull Material material) { + this(material, 0); + } + + private LegacyItem(final @NonNull String materialName, final @NonNull String legacyMaterialName, + final int legacyData) { + this(Material.valueOf(LEGACY_MATERIALS ? legacyMaterialName : materialName), (byte) legacyData); + } + + private LegacyItem(final @NonNull String materialName, final @NonNull String legacyMaterialName) { + this(materialName, legacyMaterialName, 0); + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/NmsUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/NmsUtil.java new file mode 100644 index 000000000..b665dde9e --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/NmsUtil.java @@ -0,0 +1,280 @@ +package ru.progrm_jarvis.minecraft.commons.nms; + +import com.comphenix.protocol.wrappers.WrappedDataWatcher; +import lombok.*; +import lombok.experimental.UtilityClass; +import lombok.extern.java.Log; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; +import ru.progrm_jarvis.javacommons.invoke.InvokeUtil; +import ru.progrm_jarvis.minecraft.commons.nms.metadata.DataWatcherFactory; +import ru.progrm_jarvis.minecraft.commons.nms.metadata.DataWatcherFactory.DataWatcherModifier; +import ru.progrm_jarvis.minecraft.commons.nms.metadata.LegacyDataWatcherFactory; +import ru.progrm_jarvis.minecraft.commons.nms.metadata.StandardDataWatcherFactory; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.lang.invoke.MethodHandles.insertArguments; +import static java.lang.invoke.MethodType.methodType; + +/** + * Utility for NMS-related features + */ +@Log +@UtilityClass +public class NmsUtil { + + private static final MethodHandles.@NotNull Lookup LOOKUP = MethodHandles.lookup(); + + private final @NotNull NmsVersion CRAFT_BUKKIT_VERSION = NmsVersion.computeCurrent(); + + /** + * Base package of NMS (net.minecraft.server.{version}) + * + * @deprecated since Spigot 1.17, official Mojang Mappings are used + */ + @Deprecated + private final @NotNull String NMS_PACKAGE = "net.minecraft.server." + CRAFT_BUKKIT_VERSION.getName(); + + /** + * Base package of CraftBukkit (org.bukkit.craftbukkit.{version}) + */ + private final @NotNull String CRAFT_BUKKIT_PACKAGE = "org.bukkit.craftbukkit." + CRAFT_BUKKIT_VERSION.getName(); + + private final @NotNull MethodHandle NEXT_ENTITY_ID; + + static { + Class minecraftVersionClass; + try { + minecraftVersionClass = Class.forName("net.minecraft.MinecraftVersion"); + } catch (final ClassNotFoundException e) { + minecraftVersionClass = null; + log.info("Current Minecraft Server version used legacy NMS structure"); + } + + val legacyMappings = minecraftVersionClass == null; + log.info(() -> "Using " + (legacyMappings ? "legacy" : "official") + " Minecraft Server mapping"); + + final Class nmsEntityClass; + { + val nmsEntityClassName = (legacyMappings ? getNmsPackage() : "net.minecraft.world.entity") + ".Entity"; + try { + nmsEntityClass = Class.forName(nmsEntityClassName); + } catch (final ClassNotFoundException e) { + throw new InternalError("Cannot find entity class by name \"" + nmsEntityClassName + '"', e); + } + } + + entityIdImplementation: { + // Paper has a method for generating entity ID + paperApi: { + @SuppressWarnings("deprecation") val unsafe = Bukkit.getUnsafe(); + val unsafeValuesClass = unsafe.getClass(); + val nextEntityIdMethodType = methodType(int.class); + final MethodHandle nextEntityIdMethodHandle; + try { + nextEntityIdMethodHandle = LOOKUP + .findVirtual(unsafeValuesClass, "nextEntityId", nextEntityIdMethodType); + } catch (final NoSuchMethodException | IllegalAccessException e) { + // required Paper API is not available + break paperApi; + } + + NEXT_ENTITY_ID = insertArguments(nextEntityIdMethodHandle, 0, unsafe); + + break entityIdImplementation; + } + + if (!legacyMappings) { + for (val field : nmsEntityClass.getDeclaredFields()) { // trust field order + val modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers) + && AtomicInteger.class.isAssignableFrom(field.getType())) { + NEXT_ENTITY_ID = createAtomicIntegerNextEntityId(field); + + break entityIdImplementation; + } + } + + throw new InternalError("Cannot find entity count field in " + nmsEntityClass); + } + + final Field entityCountField; + try { + entityCountField = nmsEntityClass.getDeclaredField("entityCount"); + } catch (NoSuchFieldException e) { + throw new InternalError("Cannot find field " + nmsEntityClass.getCanonicalName() + "#entityCount", e); + } + + final Class entityCountFieldType; + if (AtomicInteger.class.isAssignableFrom(entityCountFieldType = entityCountField.getType())) NEXT_ENTITY_ID + = createAtomicIntegerNextEntityId(entityCountField); + else if (entityCountFieldType == int.class) NEXT_ENTITY_ID = createIntNextEntityId(entityCountField); + else throw new InternalError( + "Field `nmsEntityClass#" + entityCountField + + "` is of unsupported type `" + entityCountFieldType + '`' + ); + } + } + + /** + * DataWatcher factory valid for current server version + */ + private final DataWatcherFactory DATA_WATCHER_FACTORY = CRAFT_BUKKIT_VERSION.getGeneration() < 9 + ? LegacyDataWatcherFactory.create() : StandardDataWatcherFactory.create(); + + /** + * Gets version of the current server. + * + * @return version of this server + */ + public @NotNull NmsVersion getVersion() { + return CRAFT_BUKKIT_VERSION; + } + + /** + * Gets base package of NMS (net.minecraft.server.{version}) + * + * @return base package of NMS + * + * @deprecated since Spigot 1.17, official Mojang Mappings are used + */ + @Deprecated // this is no longer required for Minecraft 1.17 + public @NotNull String getNmsPackage() { + return NMS_PACKAGE; + } + + /** + * Gets base package of CraftBukkit (org.bukkit.craftbukkit.{version}) + * + * @return base package of CraftBukkit + */ + public @NotNull String getCraftBukkitPackage() { + return CRAFT_BUKKIT_PACKAGE; + } + + /** + * Gets DataWatcher factory valid for current server version. + * + * @return DataWatcher factory valid for current server version + */ + public static @NotNull DataWatcherFactory getDataWatcherFactory() { + return DATA_WATCHER_FACTORY; + } + + /** + * Creates new DataWatcher modifier valid for current server version. + * + * @return DataWatcher modifier valid for current server version + */ + public @NotNull DataWatcherModifier dataWatcherModifier() { + return DATA_WATCHER_FACTORY.modifier(); + } + + /** + * Creates new DataWatcher modifier from DataWatcher specified which valid for current server version. + * + * @param dataWatcher DataWatcher from which to create DataWatcher modifier + * @return DataWatcher modifier valid for current server version + */ + public @NotNull DataWatcherModifier dataWatcherModifier(final @NonNull WrappedDataWatcher dataWatcher) { + return DATA_WATCHER_FACTORY.modifier(dataWatcher); + } + + /** + * Gets ID for entity preventing from conflicts with real entities. + * + * @return new ID for an entity + */ + @SneakyThrows // `MethodHandles#invokeExact()` + public int nextEntityId() { + return (int) NEXT_ENTITY_ID.invokeExact(); + } + + @SneakyThrows // MethodHandle#invokeExact(..) + private static @NotNull MethodHandle createAtomicIntegerNextEntityId(final @NotNull Field atomicIntegerField) { + val type = methodType(int.class); + final MethodHandle methodHandle; + try { + methodHandle = LOOKUP.findVirtual(AtomicInteger.class, "incrementAndGet", type); + } catch (final NoSuchMethodException | IllegalAccessException e) { + throw new AssertionError("Failed to find `AtomicInteger#incrementAndGet()` method", e); + } + + return insertArguments( + methodHandle, 0, (AtomicInteger) InvokeUtil.toGetterMethodHandle(atomicIntegerField).invokeExact() + ); + } + + @SneakyThrows // MethodHandle#invokeExact(..) + private static @NotNull MethodHandle createIntNextEntityId(final @NotNull Field intField) { + val type = methodType(int.class, MethodHandle.class, MethodHandle.class); + final MethodHandle methodHandle; + try { + methodHandle = LOOKUP.findStatic(NmsUtil.class, "getAndIncrementInt", type); + } catch (final NoSuchMethodException | IllegalAccessException e) { + throw new AssertionError("Failed to find `AtomicInteger#incrementAndGet()` method", e); + } + + return insertArguments( + methodHandle, 0, + InvokeUtil.toGetterMethodHandle(intField), + InvokeUtil.toSetterMethodHandle(intField) + ); + } + + @SneakyThrows // `MethodHandle#invokeExact(..)` + private static int getAndIncrementInt(final @NotNull MethodHandle getter, final @NotNull MethodHandle setter) { + if (!Bukkit.isPrimaryThread()) throw new IllegalStateException( + "Entity IDs should only be generated on main Bukkit thread" + ); + + val id = (int) getter.invokeExact(); + setter.invokeExact(id + 1); + + return id; + } + + /** + * Version of a server. + */ + @Value + @RequiredArgsConstructor + public static class NmsVersion { + + /** + * Name of the version + */ + @NonNull String name; + + /** + * Generation of a version (such as 13 for minecraft 1.13.2) + */ + short generation; + + /** + * Constructs a new NMS version by name specified (such as v1_12_R1). + * + * @param name name of a version + */ + private NmsVersion(final @NonNull String name) { + this(name, Short.parseShort(name.substring(3, name.indexOf('_', 4)))); + } + + /** + * Computes current version of NMS for this server + * based on implementation of {@link org.bukkit.Server} accessible via {@link Bukkit#getServer()}. + * + * @return current NMS version + */ + public static NmsVersion computeCurrent() { + val craftServerPackage = Bukkit.getServer().getClass().getPackage().getName(); + + return new NmsVersion(craftServerPackage.substring(craftServerPackage.lastIndexOf('.') + 1)); + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/ProtocolLibConversions.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/ProtocolLibConversions.java new file mode 100644 index 000000000..a3d16ec9d --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/ProtocolLibConversions.java @@ -0,0 +1,79 @@ +package ru.progrm_jarvis.minecraft.commons.nms; + +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.wrappers.BlockPosition; +import com.comphenix.protocol.wrappers.EnumWrappers; +import com.comphenix.protocol.wrappers.EnumWrappers.Direction; +import com.comphenix.protocol.wrappers.EnumWrappers.Particle; +import com.comphenix.protocol.wrappers.Vector3F; +import lombok.experimental.UtilityClass; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Utilities for conversions between NSM and Protocol Lib conversions. + */ +@UtilityClass +public class ProtocolLibConversions { + + @Contract("null -> null; !null -> !null") + public @Nullable Object toNms(final @Nullable Vector3F vector3F) { + return vector3F == null ? null : Vector3FConverterHolder.INSTANCE.getGeneric(vector3F); + } + + @Contract("null -> null; !null -> !null") + public @Nullable Vector3F toVector3F(final @Nullable Object nms) { + return nms == null ? null : Vector3FConverterHolder.INSTANCE.getSpecific(nms); + } + + @Contract("null -> null; !null -> !null") + public @Nullable Object toNms(final @Nullable Direction direction) { + return direction == null ? null : DirectionConverterHolder.INSTANCE.getGeneric(direction); + } + + @Contract("null -> null; !null -> !null") + public @Nullable Direction toDirection(final @Nullable Object nms) { + return nms == null ? null : DirectionConverterHolder.INSTANCE.getSpecific(nms); + } + + @Contract("null -> null; !null -> !null") + public @Nullable Object toNms(final @Nullable Particle particle) { + return particle == null ? null : ParticleConverterHolder.INSTANCE.getGeneric(particle); + } + + @Contract("null -> null; !null -> !null") + public @Nullable BlockPosition toBlockPosition(final @Nullable Object nms) { + return nms == null ? null : BlockPositionConverterHolder.INSTANCE.getSpecific(nms); + } + + @Contract("null -> null; !null -> !null") + public @Nullable Object toNms(final @Nullable BlockPosition blockPosition) { + return blockPosition == null ? null : BlockPositionConverterHolder.INSTANCE.getGeneric(blockPosition); + } + + @Contract("null -> null; !null -> !null") + public @Nullable Particle toParticle(final @Nullable Object nms) { + return nms == null ? null : ParticleConverterHolder.INSTANCE.getSpecific(nms); + } + + @UtilityClass + private final class Vector3FConverterHolder { + private final @NotNull EquivalentConverter<@NotNull Vector3F> INSTANCE = Vector3F.getConverter(); + } + + @UtilityClass + private final class DirectionConverterHolder { + private final @NotNull EquivalentConverter<@NotNull Direction> INSTANCE = EnumWrappers.getDirectionConverter(); + } + + @UtilityClass + private final class ParticleConverterHolder { + private final @NotNull EquivalentConverter<@NotNull Particle> INSTANCE = EnumWrappers.getParticleConverter(); + } + + @UtilityClass + private final class BlockPositionConverterHolder { + private final @NotNull EquivalentConverter<@NotNull BlockPosition> INSTANCE = BlockPosition.getConverter(); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/metadata/DataWatcherFactory.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/metadata/DataWatcherFactory.java new file mode 100644 index 000000000..154d8c040 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/metadata/DataWatcherFactory.java @@ -0,0 +1,721 @@ +package ru.progrm_jarvis.minecraft.commons.nms.metadata; + +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.*; +import com.comphenix.protocol.wrappers.EnumWrappers.Direction; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import lombok.NonNull; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.progrm_jarvis.minecraft.commons.nms.ProtocolLibConversions; + +import java.util.UUID; + +public interface DataWatcherFactory { + + // Modifier creation + + /** + * Creates new modifier for {@link WrappedDataWatcher} specified. + * + * @param watcher which to use as modifier backend + * @return created modifier + */ + @NotNull DataWatcherModifier modifier(WrappedDataWatcher watcher); + + /** + * Creates new modifier of {@link WrappedDataWatcher}. + * + * @return created modifier + */ + @NotNull DataWatcherModifier modifier(); + + // Actual types + + /** + * Creates watchable object for {@code byte} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchable(int id, byte value); + + /** + * Creates watchable object for {@code int} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchable(int id, int value); + + /** + * Creates watchable object for {@code float} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchable(int id, float value); + + /** + * Creates watchable object for {@link String} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchable(int id, String value); + + /** + * Creates watchable object for {@code IChatBaseComponent} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableIChatBaseComponent(int id, @NonNull Object value); + + /** + * Creates watchable object for {@link WrappedChatComponent} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + default @NotNull WrappedWatchableObject createWatchable(final int id, final @NonNull WrappedChatComponent value) { + return createWatchableIChatBaseComponent(id, value.getHandle()); + } + + /** + * Creates watchable object for optional {@code IChatBaseComponent} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableOptionalIChatBaseComponent(int id, + @Nullable Object value); + + /** + * Creates watchable object for optional {@link WrappedChatComponent} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + default @NotNull WrappedWatchableObject createWatchableOptional(final int id, + final @Nullable WrappedChatComponent value) { + return createWatchableOptionalIChatBaseComponent(id, value == null ? null : value.getHandle()); + } + + /** + * Creates watchable object for NMS {@code ItemStack} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableItemStack(int id, Object value); + + /** + * Creates watchable object for {@link ItemStack} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + default @NotNull WrappedWatchableObject createWatchable(final int id, final ItemStack value) { + return createWatchableItemStack(id, MinecraftReflection.getMinecraftItemStack(value)); + } + + /** + * Creates watchable object for {@code boolean} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchable(int id, boolean value); + + /** + * Creates watchable object for {@code Vector3f} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableVector3f(int id, @NonNull Object value); + + /** + * Creates watchable object for {@link Vector3F} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + default @NotNull WrappedWatchableObject createWatchable(final int id, final @NonNull Vector3F value) { + return createWatchableVector3f(id, ProtocolLibConversions.toNms(value)); + } + + /** + * Creates watchable object for {@code BlockPosition} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableBlockPosition(int id, @NonNull Object value); + + /** + * Creates watchable object for {@link BlockPosition} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + default @NotNull WrappedWatchableObject createWatchable(final int id, final @NonNull BlockPosition value) { + return createWatchableBlockPosition(id, ProtocolLibConversions.toNms(value)); + } + + /** + * Creates watchable object for optional {@code BlockPosition} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableOptionalBlockPosition(int id, @Nullable Object value); + + /** + * Creates watchable object for optional {@link BlockPosition} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + default @NotNull WrappedWatchableObject createWatchableOptional(final int id, final @Nullable BlockPosition value) { + return createWatchableOptionalBlockPosition(id, ProtocolLibConversions.toBlockPosition(value)); + } + + /** + * Creates watchable object for {@code EnumDirection} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableEnumDirection(int id, @NonNull Object value); + + /** + * Creates watchable object for {@link Direction} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + default @NotNull WrappedWatchableObject createWatchable(final int id, final @NonNull Direction value) { + return createWatchableEnumDirection(id, ProtocolLibConversions.toNms(value)); + } + + /** + * Creates watchable object for optional {@link UUID} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableOptional(int id, @Nullable UUID value); + + /** + * Creates watchable object for optional {@code IBlockData} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableOptionalIBlockData(int id, @Nullable Object value); + + /** + * Creates watchable object for optional {@link WrappedBlockData} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + default @NotNull WrappedWatchableObject createWatchableOptional(final int id, + final @Nullable WrappedBlockData value) { + return createWatchableOptionalIBlockData(id, value == null ? null : value.getHandle()); + } + + /** + * Creates watchable object for {@code NBTTagCompound} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableNBTTagCompound(int id, @NonNull Object value); + + /** + * Creates watchable object for {@code NBTTagCompound} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + default @NotNull WrappedWatchableObject createWatchable(final int id, final @NonNull NbtCompound value) { + return createWatchableNBTTagCompound(id, value.getHandle()); + } + + /** + * Creates watchable object for {@code Particle} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableParticle(int id, @NonNull Object value); + + /** + * Creates watchable object for {@link EnumWrappers.Particle} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + default @NotNull WrappedWatchableObject createWatchable(final int id, final @NonNull WrappedParticle value) { + return createWatchableParticle(id, value.getHandle()); + } + + /** + * Creates watchable object for {@code VillagerData} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableVillagerData(int id, @NonNull Object value); + + /** + * Creates watchable object for {@link EnumWrappers.Particle} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + default @NotNull WrappedWatchableObject createWatchable(final int id, final @NonNull WrappedVillagerData value) { + return createWatchableVillagerData(id, value.getHandle()); + } + + /** + * Creates watchable object for optional {@code int} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableOptional(int id, @Nullable Integer value); + + /** + * Creates watchable object for {@code EntityPose} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchableEntityPose(int id, @NonNull Object value); + + /** + * Creates watchable object for {@link EnumWrappers.EntityPose} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + default @NotNull WrappedWatchableObject createWatchable(final int id, + final @NonNull EnumWrappers.EntityPose value) { + return createWatchableEntityPose(id, value.toNms()); + } + + // Unsupported types + + /** + * Creates watchable object for {@code short} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + */ + @NotNull WrappedWatchableObject createWatchable(int id, short value); + + /** + * Creates watchable object for {@link Object} value at index specified. + * + * @param id id of a value + * @param value value + * @return created watchable object + * + * @apiNote this should only be used if there is no type-safe on NMS-specific analog + */ + // should be used if and only if none of default #set[..](id, value) methods don't provide type given + @NotNull WrappedWatchableObject createWatchableObject(int id, Object value); + + /** + * Modifier of {@link WrappedDataWatcher} which applies all changes to DataWatcher. + */ + interface DataWatcherModifier { + + /** + * Returns DataWatcher modified. + * + * @return DataWatcher modified + */ + @NotNull WrappedDataWatcher dataWatcher(); + + /** + * Returns deep clone of DataWatcher modified. + * + * @return clone of DataWatcher modified + */ + default @NotNull WrappedDataWatcher dataWatcherClone() { + return dataWatcher().deepClone(); + } + + /** + * Deeply clones this modifier to a new one. + * + * @return this modifier's copy + */ + @NotNull DataWatcherModifier clone(); + + // Actual types + + /** + * Sets DataWatcher's modifier to specified {@code byte} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier set(int id, byte value); + + /** + * Sets DataWatcher's modifier to specified {@code int} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier set(int id, int value); + + /** + * Sets DataWatcher's modifier to specified {@code float} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier set(int id, float value); + + /** + * Sets DataWatcher's modifier to specified {@link String} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier set(int id, @NonNull String value); + + /** + * Sets DataWatcher's modifier to specified {@code IChatBaseComponent} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setIChatBaseComponent(int id, @NonNull Object value); + + /** + * Sets DataWatcher's modifier to specified {@link WrappedChatComponent} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + default @NotNull DataWatcherModifier set(final int id, final @NonNull WrappedChatComponent value) { + return setIChatBaseComponent(id, value.getHandle()); + } + + /** + * Sets DataWatcher's modifier to specified optional {@code IChatBaseComponent} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setOptionalIChatBaseComponent(int id, @Nullable Object value); + + /** + * Sets DataWatcher's modifier to specified optional {@link WrappedChatComponent} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + default @NotNull DataWatcherModifier setOptional(final int id, final @Nullable WrappedChatComponent value) { + return setOptionalIChatBaseComponent(id, value == null ? null : value.getHandle()); + } + + /** + * Sets DataWatcher's modifier to specified NMS {@code ItemStack} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setItemStack(int id, @NonNull Object value); + + /** + * Sets DataWatcher's modifier to specified {@link ItemStack} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + default @NotNull DataWatcherModifier set(final int id, final @NonNull ItemStack value) { + return setItemStack(id, MinecraftReflection.getMinecraftItemStack(value)); + } + + /** + * Sets DataWatcher's modifier to specified {@code boolean} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier set(int id, boolean value); + + /** + * Sets DataWatcher's modifier to specified {@code Vector3f} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setVector3f(int id, @NonNull Object value); + + /** + * Sets DataWatcher's modifier to specified {@link Vector3F} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + default @NotNull DataWatcherModifier set(final int id, final @NonNull Vector3F value) { + return setVector3f(id, ProtocolLibConversions.toNms(value)); + } + + /** + * Sets DataWatcher's modifier to specified {@code BlockPosition} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setBlockPosition(int id, @NonNull Object value); + + /** + * Sets DataWatcher's modifier to specified {@link BlockPosition} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + default @NotNull DataWatcherModifier set(int id, final @NonNull BlockPosition value) { + return setBlockPosition(id, ProtocolLibConversions.toNms(value)); + } + + /** + * Sets DataWatcher's modifier to specified optional {@code BlockPosition} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setOptionalBlockPosition(int id, @Nullable Object value); + + /** + * Sets DataWatcher's modifier to specified optional {@link BlockPosition} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + default @NotNull DataWatcherModifier setOptional(final int id, final @Nullable BlockPosition value) { + return setOptionalBlockPosition(id, ProtocolLibConversions.toNms(value)); + } + + /** + * Sets DataWatcher's modifier to specified {@code EnumDirection} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setEnumDirection(int id, @NonNull Object value); + + /** + * Sets DataWatcher's modifier to specified {@link Direction} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + default @NotNull DataWatcherModifier set(final int id, final @NonNull Direction value) { + return setEnumDirection(id, ProtocolLibConversions.toNms(value)); + } + + /** + * Sets DataWatcher's modifier to specified optional {@link UUID} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setOptional(int id, @Nullable UUID value); + + /** + * Sets DataWatcher's modifier to specified optional {@code IBlockData} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setOptionalIBlockData(int id, @Nullable Object value); + + /** + * Sets DataWatcher's modifier to specified optional {@link WrappedBlockData} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + default @NotNull DataWatcherModifier setOptional(final int id, final @Nullable WrappedBlockData value) { + return setOptionalIBlockData(id, value == null ? null : value.getHandle()); + } + + /** + * Sets DataWatcher's modifier to specified {@code NBTTagCompound} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setNBTTagCompound(int id, @NonNull Object value); + + /** + * Sets DataWatcher's modifier to specified {@link NbtCompound} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + default @NotNull DataWatcherModifier set(final int id, final @NonNull NbtCompound value) { + return setNBTTagCompound(id, value.getHandle()); + } + + /** + * Sets DataWatcher's modifier to specified {@code Particle} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setParticle(int id, @NonNull Object value); + + /** + * Sets DataWatcher's modifier to specified {@link NbtCompound} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + default @NotNull DataWatcherModifier set(final int id, final @NonNull WrappedParticle value) { + return setParticle(id, value.getHandle()); + } + + /** + * Sets DataWatcher's modifier to specified {@code VillagerData} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setVillagerData(int id, @NonNull Object value); + + /** + * Sets DataWatcher's modifier to specified {@link WrappedVillagerData} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + default @NotNull DataWatcherModifier set(final int id, final @NonNull WrappedVillagerData value) { + return setVillagerData(id, value.getHandle()); + } + + /** + * Sets DataWatcher's modifier to specified optional {@code int} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setOptional(int id, @Nullable Integer value); + + /** + * Sets DataWatcher's modifier to specified {@code VillagerData} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier setEntityPose(int id, @NonNull Object value); + + /** + * Sets DataWatcher's modifier to specified {@link EnumWrappers.EntityPose} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + default @NotNull DataWatcherModifier set(final int id, final @NonNull EnumWrappers.EntityPose value) { + return setEntityPose(id, value.toNms()); + } + + // Unsupported types + + /** + * Sets DataWatcher's modifier to specified {@code short} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + */ + @NotNull DataWatcherModifier set(int id, short value); + + /** + * Sets DataWatcher's modifier to specified {@link Object} value at specified index. + * + * @param id id of a value + * @param value value to set at id + * @return this DataWatcher builder + * + * @apiNote this should only be used if there is no type-safe on NMS-specific analog + */ + @NotNull DataWatcherModifier setObject(int id, Object value); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/metadata/LegacyDataWatcherFactory.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/metadata/LegacyDataWatcherFactory.java new file mode 100644 index 000000000..fa90ce1b1 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/metadata/LegacyDataWatcherFactory.java @@ -0,0 +1,317 @@ +package ru.progrm_jarvis.minecraft.commons.nms.metadata; + +import com.comphenix.protocol.wrappers.WrappedDataWatcher; +import com.comphenix.protocol.wrappers.WrappedWatchableObject; +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.UUID; + +/** + * DataWatcher factory for pre 1.9 versions. + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class LegacyDataWatcherFactory implements DataWatcherFactory { + + public static @NotNull DataWatcherFactory create() { + return new LegacyDataWatcherFactory(); + } + + @Override + public @NotNull DataWatcherModifier modifier(final @NonNull WrappedDataWatcher watcher) { + return new LegacyDataWatcherModifier(watcher); + } + + @Override + public @NotNull DataWatcherModifier modifier() { + return new LegacyDataWatcherModifier(new WrappedDataWatcher()); + } + + // Actual types + + @Override + public @NotNull WrappedWatchableObject createWatchable(final int id, final byte value) { + return new WrappedWatchableObject(id, value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchable(final int id, final int value) { + return new WrappedWatchableObject(id, value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchable(final int id, final float value) { + return new WrappedWatchableObject(id, value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchable(final int id, final String value) { + return new WrappedWatchableObject(id, value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableIChatBaseComponent(final int id, + final @NonNull Object value) { + return new WrappedWatchableObject(id, value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableOptionalIChatBaseComponent(final int id, + final @Nullable Object value) { + return new WrappedWatchableObject(id, Optional.ofNullable(value)); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableItemStack(final int id, final Object value) { + return new WrappedWatchableObject(id, value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchable(final int id, final boolean value) { + return createWatchable(id, value ? (byte) 0x1 : (byte) 0x0); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableVector3f(final int id, final @NonNull Object value) { + return new WrappedWatchableObject(id, value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableBlockPosition(final int id, final @NonNull Object value) { + return new WrappedWatchableObject(id, value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableOptionalBlockPosition(final int id, + final @Nullable Object value) { + return new WrappedWatchableObject(id, Optional.ofNullable(value)); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableEnumDirection(final int id, final @NonNull Object value) { + return new WrappedWatchableObject(id, value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableOptional(final int id, final @Nullable UUID value) { + return new WrappedWatchableObject(id, Optional.ofNullable(value)); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableOptionalIBlockData(final int id, + final @Nullable Object value) { + return new WrappedWatchableObject(id, Optional.ofNullable(value)); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableNBTTagCompound(final int id, final @NonNull Object value) { + return new WrappedWatchableObject(id, value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableParticle(final int id, final @NonNull Object value) { + return new WrappedWatchableObject(id, value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableVillagerData(final int id, final @NonNull Object value) { + return new WrappedWatchableObject(id, value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableOptional(final int id, final @Nullable Integer value) { + return new WrappedWatchableObject(id, Optional.ofNullable(value)); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableEntityPose(final int id, final @NonNull Object value) { + return new WrappedWatchableObject(id, value); + } + + // Unsupported types + + @Override + public @NotNull WrappedWatchableObject createWatchable(final int id, final short value) { + return new WrappedWatchableObject(id, value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableObject(final int id, final Object value) { + return new WrappedWatchableObject(id, value); + } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + private static final class LegacyDataWatcherModifier implements DataWatcherFactory.DataWatcherModifier { + + @NotNull WrappedDataWatcher dataWatcher; + + @Override + public @NotNull WrappedDataWatcher dataWatcher() { + return dataWatcher; + } + + @Override + @SuppressWarnings("MethodDoesntCallSuperMethod") + public @NotNull DataWatcherFactory.DataWatcherModifier clone() { + return new LegacyDataWatcherModifier(dataWatcher.deepClone()); + } + + // Actual types + + @Override + public @NotNull DataWatcherModifier set(final int id, final byte value) { + dataWatcher.setObject(id, value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier set(final int id, final int value) { + dataWatcher.setObject(id, value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier set(final int id, final float value) { + dataWatcher.setObject(id, value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier set(final int id, final @NonNull String value) { + dataWatcher.setObject(id, value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setIChatBaseComponent(final int id, final @NonNull Object value) { + dataWatcher.setObject(id, value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setOptionalIChatBaseComponent(final int id, final @Nullable Object value) { + dataWatcher.setObject(id, Optional.ofNullable(value)); + + return this; + } + + + @Override + public @NotNull DataWatcherModifier setItemStack(final int id, final @NonNull Object value) { + dataWatcher.setObject(id, value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier set(final int id, final boolean value) { + dataWatcher.setObject(id, value ? (byte) 0x1 : (byte) 0x0); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setVector3f(final int id, final @NonNull Object value) { + dataWatcher.setObject(id, value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setBlockPosition(final int id, final @NonNull Object value) { + dataWatcher.setObject(id, value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setOptionalBlockPosition(final int id, final @Nullable Object value) { + dataWatcher.setObject(id, Optional.ofNullable(value)); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setEnumDirection(final int id, final @NonNull Object value) { + dataWatcher.setObject(id, value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setOptional(final int id, final @Nullable UUID value) { + dataWatcher.setObject(id, Optional.ofNullable(value)); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setOptionalIBlockData(final int id, final @Nullable Object value) { + dataWatcher.setObject(id, Optional.ofNullable(value)); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setNBTTagCompound(final int id, final @NonNull Object value) { + dataWatcher.setObject(id, value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setParticle(final int id, final @NonNull Object value) { + dataWatcher.setObject(id, value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setVillagerData(final int id, final @NonNull Object value) { + dataWatcher.setObject(id, value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setOptional(final int id, final @Nullable Integer value) { + dataWatcher.setObject(id, Optional.ofNullable(value)); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setEntityPose(final int id, final @NonNull Object value) { + dataWatcher.setObject(id, value); + + return this; + } + + // Unsupported types + + @Override + public @NotNull DataWatcherModifier set(final int id, final short value) { + dataWatcher.setObject(id, value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setObject(final int id, final Object value) { + dataWatcher.setObject(id, value); + + return this; + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/metadata/MetadataGenerator.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/metadata/MetadataGenerator.java new file mode 100644 index 000000000..cfb1df83e --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/metadata/MetadataGenerator.java @@ -0,0 +1,2264 @@ +package ru.progrm_jarvis.minecraft.commons.nms.metadata; + +import com.comphenix.protocol.wrappers.*; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import lombok.*; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +import lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.DyeColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.progrm_jarvis.minecraft.commons.nms.NmsUtil; + +import java.util.UUID; + +/** + *

Editor for Metadata of {@link WrappedWatchableObject} providing classes + * containing static methods for developer-friendly object creation.

+ * + *

Version specifications:

+ * + */ +@UtilityClass +@SuppressWarnings({ + "ClassWithOnlyPrivateConstructors", // "sealed" classes + "unused", // no way to correctly test methods + "EmptyClass", "NonFinalUtilityClass", // ierarchy of entities + "TypeMayBeWeakened" // enums implementing local interface +}) +public class MetadataGenerator { + + private final int VERSION = NmsUtil.getVersion().getGeneration(); + private final @NonNull DataWatcherFactory FACTORY = NmsUtil.getDataWatcherFactory(); + + private static void requireAtLeast(final int minVersion) { + if (VERSION < minVersion) throw new UnsupportedOperationException( + "This is not supported on versions prior to 1." + minVersion + ); + } + + private static void requireAtMost(final int maxVersion) { + if (VERSION > maxVersion) throw new UnsupportedOperationException( + "This is not supported on versions after 1." + maxVersion + ); + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Entity { + + public static @NotNull WrappedWatchableObject entityFlags(final @NotNull EntityFlag @NonNull ...flags) { + return FACTORY.createWatchable(0, ByteFlag.allChecked(flags)); + } + + public static @NotNull WrappedWatchableObject air(final int air) { + return VERSION >= 9 ? FACTORY.createWatchable(1, air) : FACTORY.createWatchable(1, (short) air); + } + + public static @NotNull WrappedWatchableObject air(final short air) { + return VERSION >= 9 ? FACTORY.createWatchable(1, (int) air) : FACTORY.createWatchable(1, air); + } + + public static @NotNull WrappedWatchableObject name(final @Nullable WrappedChatComponent name) { + requireAtLeast(9); + + return VERSION >= 13 + ? FACTORY.createWatchableOptional(2, name) + : FACTORY.createWatchable(2, name == null ? "" : name.getJson()); + } + + public static @NotNull WrappedWatchableObject name(final @Nullable String name) { + requireAtLeast(9); + + return VERSION >= 13 + ? FACTORY.createWatchableOptional(2, name == null ? null : WrappedChatComponent.fromText(name)) + : FACTORY.createWatchable(2, name == null ? "" : name); + } + + public static @NotNull WrappedWatchableObject nameVisible(final boolean nameVisible) { + requireAtLeast(9); + + return FACTORY.createWatchable(3, nameVisible); + } + + public static @NotNull WrappedWatchableObject silent(final boolean silent) { + return FACTORY.createWatchable(4, silent); + } + + public static @NotNull WrappedWatchableObject noGravity(final boolean noGravity) { + requireAtLeast(10); + + return FACTORY.createWatchable(5, noGravity); + } + + public static @NotNull WrappedWatchableObject pose(final EnumWrappers.EntityPose pose) { + requireAtLeast(14); + + return FACTORY.createWatchable(6, pose); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum EntityFlag implements ByteFlag { + ON_FIRE((byte) 0x01), + CROUCHING((byte) 0x02), + RIDING(VERSION >= 9 ? UNSUPPORTED : (byte) 0x04), + SPRINTING((byte) 0x08), + INTERACTING(VERSION >= 11 ? UNSUPPORTED : (byte) 0x10), + SWIMMING(VERSION >= 11 ? (byte) 0x10 : UNSUPPORTED), + INVISIBLE((byte) 0x20), + GLOWING(VERSION >= 9 ? (byte) 0x40 : UNSUPPORTED); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Projectile extends Entity {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class ItemedThrowable extends Projectile {} // note: there is no method as it's child-dependant + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Egg extends ItemedThrowable { + + public static @NotNull WrappedWatchableObject item(final @NonNull ItemStack item) { + requireAtLeast(14); + + return FACTORY.createWatchable(7, item); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class EnderPearl extends ItemedThrowable { + + public static @NotNull WrappedWatchableObject item(final @NonNull ItemStack item) { + requireAtLeast(14); + + return FACTORY.createWatchable(7, item); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class ExperienceBottle extends ItemedThrowable { + + public static @NotNull WrappedWatchableObject item(final @NonNull ItemStack item) { + requireAtLeast(14); + + return FACTORY.createWatchable(7, item); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Snowball extends ItemedThrowable { + + public static @NotNull WrappedWatchableObject item(final @NonNull ItemStack item) { + requireAtLeast(14); + + return FACTORY.createWatchable(7, item); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class EyeOfEnder extends ItemedThrowable { + + public static @NotNull WrappedWatchableObject item(final @NonNull ItemStack item) { + requireAtLeast(14); + + return FACTORY.createWatchable(7, item); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Potion extends ItemedThrowable { + + public static @NotNull WrappedWatchableObject potion(final @NonNull ItemStack potion) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 14 ? 7 : VERSION >= 10 ? 6 : 5, potion); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class FallingBlock extends Entity { + + public static @NotNull WrappedWatchableObject position(final @NonNull BlockPosition position) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : 5, position); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class AreaEffectCloud extends Entity { + + public static @NotNull WrappedWatchableObject radius(final float radius) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : 5, radius); + } + + public static @NotNull WrappedWatchableObject color(final int color) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 8 : VERSION >= 10 ? 7 : 6, color); + } + + public static @NotNull WrappedWatchableObject singlePoint(final boolean singlePoint) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 9 : VERSION >= 10 ? 8 : 7, singlePoint); + } + + public static @NotNull WrappedWatchableObject particle(final WrappedParticle particle) { + requireAtLeast(13); + + return FACTORY.createWatchable(VERSION >= 15 ? 10 : 9, particle); + } + + public static @NotNull WrappedWatchableObject particle(final int particle) { + requireAtLeast(10); + requireAtMost(12); + + return FACTORY.createWatchable(9, particle); + } + + public static @NotNull WrappedWatchableObject particleParameter1(final int particle) { + requireAtLeast(11); + requireAtMost(12); + + return FACTORY.createWatchable(10, particle); + } + + public static @NotNull WrappedWatchableObject particleParameter2(final int particle) { + requireAtLeast(11); + requireAtMost(12); + + return FACTORY.createWatchable(11, particle); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class FishingHook extends Entity { + + public static @NotNull WrappedWatchableObject hookedEntity(final int hookedEntityId) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : 5, hookedEntityId); + } + + public static @NotNull WrappedWatchableObject hookedEntity(final @Nullable org.bukkit.entity.Entity entity) { + requireAtLeast(9); + + return FACTORY.createWatchable( + VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : 5, + entity == null ? 0 : entity.getEntityId() + 1 + ); + } + + public static @NotNull WrappedWatchableObject catchable(final boolean catchable) { + requireAtLeast(16); + + return FACTORY.createWatchable(8, catchable); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class AbstractArrow extends Entity { + + public static @NotNull WrappedWatchableObject arrowFlags(final @NotNull AbstractArrowFlag @NonNull ...flags) { + return FACTORY.createWatchable( + VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : VERSION >= 9 ? 5 : 16, ByteFlag.allChecked(flags) + ); + } + + public static @NotNull WrappedWatchableObject shooter(final @Nullable UUID shooterUuid) { + requireAtLeast(13); + requireAtLeast(15); + + return FACTORY.createWatchableOptional(VERSION >= 15 ? 8 : 7, shooterUuid); + } + + public static @NotNull WrappedWatchableObject piercingLevel(final byte piercingLevel) { + requireAtLeast(15); + + return FACTORY.createWatchable(VERSION >= 16 ? 8 : 9, piercingLevel); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum AbstractArrowFlag implements ByteFlag { + CRITICAL((byte) 0x01), + NO_CLIP(VERSION >= 13 ? (byte) 0x02 : (byte) -1); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Arrow extends AbstractArrow { + + public static @NotNull WrappedWatchableObject color(final int color) { + requireAtLeast(16); + + return FACTORY.createWatchable(9, color); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class TippedArrow extends AbstractArrow { + + public static @NotNull WrappedWatchableObject color(final int color) { + requireAtLeast(9); + + return FACTORY.createWatchable( + VERSION >= 16 ? 9 /* Arrow */ : VERSION >= 15 ? 10 : VERSION >= 13 ? 8 : VERSION >= 10 ? 7 : 9, + color + ); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class SpectralArrow extends AbstractArrow {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Trident extends AbstractArrow { + + public static @NotNull WrappedWatchableObject loyaltyLevel(final int loyaltyLevel) { + requireAtLeast(13); + + return FACTORY.createWatchable(VERSION >= 16 ? 9 : VERSION >= 15 ? 10 : 8, loyaltyLevel); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Boat extends Entity { + + public static @NotNull WrappedWatchableObject timeSinceLastHit(final int timeSinceLastHit) { + return FACTORY.createWatchable( + VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : VERSION >= 9 ? 5 : 17, + timeSinceLastHit + ); + } + + public static @NotNull WrappedWatchableObject forwardDirection(final int forwardDirection) { + return FACTORY.createWatchable( + VERSION >= 15 ? 8 : VERSION >= 10 ? 6 : VERSION >= 9 ? 6 : 18, + forwardDirection + ); + } + + public static @NotNull WrappedWatchableObject damageTaken(final float damageTaken) { + return FACTORY.createWatchable( + VERSION >= 15 ? 9 : VERSION >= 10 ? 8 : VERSION >= 9 ? 7 : 19, + damageTaken + ); + } + + public static @NotNull WrappedWatchableObject type(final @NonNull MetadataGenerator.Boat.BoatType type) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 10 : VERSION >= 10 ? 9 : 8, type.checkedValue()); + } + + public static @NotNull WrappedWatchableObject rightPaddleTurning(final boolean rightPaddleTurning) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 11 : VERSION >= 10 ? 10 : 9, rightPaddleTurning); + } + + public static @NotNull WrappedWatchableObject leftPaddleTurning(final boolean leftPaddleTurning) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 12 : VERSION >= 10 ? 11 : 10, leftPaddleTurning); + } + + public static @NotNull WrappedWatchableObject splashTimer(final int splashTimer) { + requireAtLeast(13); + + return FACTORY.createWatchable(VERSION >= 15 ? 13 : 12, splashTimer); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum BoatType implements IntValue { + OAK(VERSION >= 9 ? 0 : UNSUPPORTED), + SPRUCE(VERSION >= 9 ? 1 : UNSUPPORTED), + BIRCH(VERSION >= 9 ? 2 : UNSUPPORTED), + JUNGLE(VERSION >= 9 ? 3 : UNSUPPORTED), + ACACIA(VERSION >= 9 ? 4 : UNSUPPORTED), + DARK_OAK(VERSION >= 9 ? 5 : UNSUPPORTED); + + @Getter int value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class EnderCrystal extends Entity { + + public static @NotNull WrappedWatchableObject position(final @NonNull BlockPosition position) { + requireAtLeast(9); + + return FACTORY.createWatchableOptional(VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : 5, position); + } + + public static @NotNull WrappedWatchableObject showBottom(final boolean showBottom) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 8 : VERSION >= 10 ? 7 : 6, showBottom); + } + + public static @NotNull WrappedWatchableObject health(final int health) { + requireAtLeast(9); + requireAtMost(9); + + return FACTORY.createWatchable(8, health); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class DragonFireball extends Entity {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class SmallFireball extends ItemedThrowable {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Fireball extends ItemedThrowable {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class WitherSkull extends Entity { + + public static @NotNull WrappedWatchableObject invulnerable(final boolean invulnerable) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : 5, invulnerable); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Fireworks extends Entity { + + public static @NotNull WrappedWatchableObject item(final @NonNull ItemStack item) { + return FACTORY.createWatchable(VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : VERSION >= 9 ? 5 : 8, item); + } + + public static @NotNull WrappedWatchableObject shooter(final @Nullable Integer shooter) { + requireAtLeast(11); + + return VERSION >= 15 + ? FACTORY.createWatchableOptional(8, shooter) + : FACTORY.createWatchable(7, shooter == null ? 0 : shooter); + } + + public static @NotNull WrappedWatchableObject shooter(final @NonNull org.bukkit.entity.Entity entity) { + requireAtLeast(11); + + val entityId = entity.getEntityId(); + return VERSION >= 15 + ? FACTORY.createWatchableOptional(8, entityId) + : FACTORY.createWatchable(7, entityId); + } + + public static @NotNull WrappedWatchableObject shotAtAngle(final boolean shotAtAngle) { + requireAtLeast(9); + + return FACTORY.createWatchable(9, shotAtAngle); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Hanging extends Entity {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class ItemFrame extends Hanging { + + public static @NotNull WrappedWatchableObject item(final @NonNull ItemStack item) { + return FACTORY.createWatchable(VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : VERSION >= 9 ? 5 : 8, item); + } + + public static @NotNull WrappedWatchableObject rotation(final int rotation) { + return FACTORY.createWatchable(VERSION >= 15 ? 8 : VERSION >= 10 ? 7 : VERSION >= 9 ? 6 : 9, rotation); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Item extends Entity { + + public static @NotNull WrappedWatchableObject item(final @NonNull ItemStack item) { + return FACTORY.createWatchable(VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : VERSION >= 9 ? 5 : 10, item); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class LivingEntity extends Entity { + + public static @NotNull WrappedWatchableObject handStates(final @NotNull HandState @NonNull ...handStates) { + requireAtLeast(9); + + return FACTORY.createWatchableObject( + VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : 5, + ByteFlag.allChecked(handStates) + ); + } + + public static @NotNull WrappedWatchableObject health(final float health) { + return FACTORY.createWatchable(VERSION >= 15 ? 8 : VERSION >= 10 ? 7 : 6, health); + } + + public static @NotNull WrappedWatchableObject potionEffectColor(final int potionEffectColor) { + return FACTORY.createWatchable(VERSION >= 15 ? 9 : VERSION >= 10 ? 8 : 7, potionEffectColor); + } + + public static @NotNull WrappedWatchableObject potionEffectAmbient(final boolean potionEffectAmbient) { + return FACTORY.createWatchable(VERSION >= 15 ? 9 : VERSION >= 10 ? 8 : 7, potionEffectAmbient); + } + + public static @NotNull WrappedWatchableObject numberOfArrows(final int numberOfArrows) { + return VERSION >= 9 + ? FACTORY.createWatchable(VERSION >= 15 ? 11 : VERSION >= 10 ? 10 : 9, numberOfArrows) + : FACTORY.createWatchable(9, (byte) numberOfArrows); + } + + public static @NotNull WrappedWatchableObject healthAddedByAbsorption(final int healthAddedByAbsorption) { + requireAtLeast(15); + + return FACTORY.createWatchable(12, healthAddedByAbsorption); + } + + public static @NotNull WrappedWatchableObject bedLocation(final @Nullable BlockPosition bedLocation) { + requireAtLeast(14); + + return FACTORY.createWatchableOptional(VERSION >= 15 ? 13 : 12, bedLocation); + } + + // since 1.9 this is part of Insentient + public static @NotNull WrappedWatchableObject noAi(final boolean noAi) { + requireAtMost(8); + + return FACTORY.createWatchable(15, noAi); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum HandState implements ByteFlag { + HAND_ACTIVE(VERSION >= 9 ? (byte) 0x01 : UNSUPPORTED), + OFFHAND(VERSION >= 9 ? (byte) 0x02 : UNSUPPORTED), + RIPTIDE_SPIN_ATTACK(VERSION >= 13 ? (byte) 0x04 : UNSUPPORTED); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Player extends LivingEntity { + + public static @NotNull WrappedWatchableObject additionalHearts(final float additionalHearts) { + return FACTORY.createWatchable( + VERSION >= 15 ? 14 : VERSION >= 10 ? 11 : VERSION >= 9 ? 10 : 17, + additionalHearts + ); + } + + public static @NotNull WrappedWatchableObject score(final int score) { + return FACTORY.createWatchable( + VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : VERSION >= 9 ? 11 : 18, + score + ); + } + + public static @NotNull WrappedWatchableObject skinParts(final @NotNull SkinPart @NonNull ...skinParts) { + return FACTORY.createWatchableObject( + VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : VERSION >= 9 ? 12 : 10, + ByteFlag.allChecked(skinParts) + ); + } + + public static @NotNull WrappedWatchableObject hideCape(final boolean hideCape) { + requireAtMost(8); + + return FACTORY.createWatchable(16, hideCape ? 0x02 : 0x00); + } + + public static @NotNull WrappedWatchableObject mainHand(final @NonNull MainHand mainHand) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 17 : VERSION >= 10 ? 14 : 13, mainHand.checkedValue()); + } + + public static @NotNull WrappedWatchableObject leftShoulderEntity( + final @NonNull NbtCompound leftShoulderEntity + ) { + requireAtLeast(12); + + return FACTORY.createWatchable(VERSION >= 15 ? 18 : 15, leftShoulderEntity); + } + + public static @NotNull WrappedWatchableObject rightShoulderEntity( + final @NonNull NbtCompound rightShoulderEntity + ) { + requireAtLeast(12); + + return FACTORY.createWatchable(VERSION >= 15 ? 19 : 16, rightShoulderEntity); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum SkinPart implements ByteFlag { + CAPE((byte) 0x01), + JACKET((byte) 0x02), + LEFT_SLEEVE((byte) 0x04), + RIGHT_SLEEVE((byte) 0x08), + LEFT_PANT((byte) 0x10), + RIGHT_PANT((byte) 0x20), + HAT((byte) 0x40); + + @Getter byte value; + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum MainHand implements ByteValue { + LEFT(VERSION >= 9 ? (byte) 0 : UNSUPPORTED), + RIGHT(VERSION >= 9 ? (byte) 1 : UNSUPPORTED); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class ArmorStand extends LivingEntity { + + public static @NotNull WrappedWatchableObject armorStandFlags(final @NotNull ArmorStandFlag @NonNull ...flags) { + return FACTORY.createWatchable( + VERSION >= 15 ? 14 : VERSION >= 10 ? 11 : 10, + ByteFlag.allChecked(flags) + ); + } + + public static @NotNull WrappedWatchableObject headRotation(final @NonNull Vector3F headRotation) { + return FACTORY.createWatchable(VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : 11, headRotation); + } + + public static @NotNull WrappedWatchableObject bodyRotation(final @NonNull Vector3F bodyRotation) { + return FACTORY.createWatchable(VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : 12, bodyRotation); + } + + public static @NotNull WrappedWatchableObject leftArmRotation(final @NonNull Vector3F leftArmRotation) { + return FACTORY.createWatchable(VERSION >= 15 ? 17 : VERSION >= 10 ? 14 : 13, leftArmRotation); + } + + public static @NotNull WrappedWatchableObject rightArmRotation(final @NonNull Vector3F rightArmRotation) { + return FACTORY.createWatchable(VERSION >= 15 ? 18 : VERSION >= 10 ? 15 : 14, rightArmRotation); + } + + public static @NotNull WrappedWatchableObject leftLegRotation(final @NonNull Vector3F leftLegRotation) { + return FACTORY.createWatchable(VERSION >= 15 ? 19 : VERSION >= 10 ? 16 : 15, leftLegRotation); + } + + public static @NotNull WrappedWatchableObject rightLegRotation(final @NonNull Vector3F rightLegRotation) { + return FACTORY.createWatchable(VERSION >= 15 ? 20 : VERSION >= 10 ? 17 : 16, rightLegRotation); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum ArmorStandFlag implements ByteFlag { + SMALL((byte) 0x01), + HAS_GRAVITY(VERSION >= 10 ? UNSUPPORTED : (byte) 0x02), + HAS_ARMS((byte) 0x04), + NO_BASE_PLATE((byte) 0x08), + MARKER((byte) 0x10); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Mob extends LivingEntity { + + public static @NotNull WrappedWatchableObject mobFlags(final @NotNull MobFlag @NonNull ...flags) { + return FACTORY.createWatchable( + VERSION >= 15 ? 14 : VERSION >= 10 ? 11 : VERSION >= 9 ? 10 : 15, + ByteFlag.allChecked(flags) + ); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum MobFlag implements ByteFlag { + NO_AI((byte) 0x01), + LEFT_HANDED(VERSION >= 9 ? (byte) 0x02 : UNSUPPORTED), + AGGRESSIVE(VERSION >= 16 ? (byte) 0x04 : UNSUPPORTED); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class AmbientCreature extends Mob {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Bat extends AmbientCreature { + + public static @NotNull WrappedWatchableObject batFlags(final @NotNull Flag @NonNull ...flags) { + return FACTORY.createWatchable( + VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : VERSION >= 9 ? 11 : 16, + ByteFlag.allChecked(flags) + ); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @SuppressWarnings("Singleton") // there just is single entry in this enum + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum Flag implements ByteFlag { + HANGING((byte) 0x01); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class PathfinderMob extends Mob {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class WaterAnimal extends PathfinderMob {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Squid extends WaterAnimal {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Dolphin extends WaterAnimal { + + public static @NotNull WrappedWatchableObject treasurePosition(final @NonNull BlockPosition treasurePosition) { + return FACTORY.createWatchable(VERSION >= 15 ? 15 : 12, treasurePosition); + } + + public static @NotNull WrappedWatchableObject canFindTreasure(final boolean canFindTreasure) { + return FACTORY.createWatchable(VERSION >= 15 ? 16 : 13, canFindTreasure); + } + + public static @NotNull WrappedWatchableObject hasFish(final boolean hasFish) { + return FACTORY.createWatchable(VERSION >= 15 ? 17 : 14, hasFish); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class AbstractFish extends WaterAnimal { + + public static @NotNull WrappedWatchableObject fromBucket(final boolean fromBucket) { + return FACTORY.createWatchable(VERSION >= 15 ? 15 : 12, fromBucket); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Cod extends AbstractFish {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class PufferFish extends AbstractFish { + + public static @NotNull WrappedWatchableObject puffState(final int puffState) { + return FACTORY.createWatchable(VERSION >= 15 ? 16 : 13, puffState); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Salmon extends AbstractFish {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class TropicalFish extends AbstractFish { + + public static @NotNull WrappedWatchableObject variant(final int variant) { + return FACTORY.createWatchable(VERSION >= 15 ? 16 : 13, variant); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class AgeableMob extends PathfinderMob { + + public static @NotNull WrappedWatchableObject baby(final boolean baby) { + return FACTORY.createWatchable(VERSION >= 15 ? 15 : 12, baby); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Animal extends AgeableMob {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class AbstractHorse extends Animal { + + public static @NotNull WrappedWatchableObject horseFlags(final @NotNull HorseFlag @NonNull ...flags) { + return FACTORY.createWatchable( + VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : VERSION >= 9 ? 12 : 16, + ByteFlag.allChecked(flags) + ); + } + + public static @NotNull WrappedWatchableObject owner(final @Nullable UUID ownerUuid) { + return VERSION >= 9 ? FACTORY.createWatchableOptional( + VERSION >= 15 ? 17 : VERSION >= 11 ? 14 : VERSION >= 10 ? 16 : 15, + ownerUuid + ) : FACTORY.createWatchable(21, ownerUuid == null ? "" : Bukkit.getOfflinePlayer(ownerUuid).getName()); + } + + public static @NotNull WrappedWatchableObject owner(final @Nullable OfflinePlayer owner) { + return VERSION >= 9 ? FACTORY.createWatchableOptional( + VERSION >= 15 ? 17 : VERSION >= 11 ? 14 : VERSION >= 10 ? 16 : 15, + owner == null ? null : owner.getUniqueId() + ) : FACTORY.createWatchable(21, owner == null ? "" : owner.getName()); + } + + public static @NotNull WrappedWatchableObject owner(final @Nullable String ownerName) { + return VERSION >= 9 ? FACTORY.createWatchableOptional( + VERSION >= 15 ? 17 : VERSION >= 11 ? 14 : VERSION >= 10 ? 16 : 15, + uuidByName(ownerName) + ) : FACTORY.createWatchable(21, ownerName == null ? "" : ownerName); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum HorseFlag implements ByteFlag { + TAME((byte) 0x02), + SADDLED((byte) 0x04), + CHEST(VERSION >= 12 ? UNSUPPORTED : (byte) 0x08), // legacy + BRED(VERSION >= 12 ? (byte) 0x08 : (byte) 0x10), + EATING(VERSION >= 12 ? (byte) 0x10 : (byte) 0x20), + REARING(VERSION >= 12 ? (byte) 0x20 : (byte) 0x40), + MOUTH_OPEN(VERSION >= 12 ? (byte) 0x40 : (byte) 0x80); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Horse extends AbstractHorse { + + public static @NotNull WrappedWatchableObject horseType(final @NonNull HorseType horseType) { + requireAtLeast(8); + requireAtMost(10); + + val value = horseType.checkedValue(); + return VERSION >= 9 + ? FACTORY.createWatchable(VERSION >= 10 ? 14 : 13, (int) value) + : FACTORY.createWatchable(19, value); + } + + public static @NotNull WrappedWatchableObject variant(final int variant) { + return FACTORY.createWatchable( + VERSION > 15 ? 18 : VERSION >= 14 ? 17 : VERSION >= 10 ? 15 : VERSION >= 9 ? 14 : 20, + variant + ); + } + + public static @NotNull WrappedWatchableObject armor(final @NonNull HorseArmor armor) { + requireAtMost(13); + + return FACTORY.createWatchable( + VERSION >= 12 ? 17 : VERSION >= 11 ? 16 : VERSION >= 10 ? 17 : VERSION >= 9 ? 16 : 22, + armor.checkedValue() + ); + } + + + public static @NotNull WrappedWatchableObject forgeArmor(final @NonNull ItemStack item) { + requireAtLeast(13); + requireAtMost(13); + + return FACTORY.createWatchable(17, item); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum HorseType implements ByteValue { + HORSE(VERSION >= 11 ? UNSUPPORTED : (byte) 0), + DONKEY(VERSION >= 11 ? UNSUPPORTED : (byte) 1), + MULE(VERSION >= 11 ? UNSUPPORTED : (byte) 2), + ZOMBIE(VERSION >= 11 ? UNSUPPORTED : (byte) 3), + SKELETON(VERSION >= 11 ? UNSUPPORTED : (byte) 4); + + @Getter byte value; + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum HorseArmor implements IntValue { + NONE(VERSION >= 14 ? UNSUPPORTED : 0), + IRON(VERSION >= 14 ? UNSUPPORTED : 1), + GOLD(VERSION >= 14 ? UNSUPPORTED : 2), + DIAMOND(VERSION >= 14 ? UNSUPPORTED : 3); + + @Getter int value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class ZombieHorse extends AbstractHorse {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class SkeletonHorse extends AbstractHorse {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class ChestedHorse extends AbstractHorse { + + public static @NotNull WrappedWatchableObject chest(final boolean chest) { + requireAtLeast(12); + + return FACTORY.createWatchable(VERSION >= 15 ? 18 : 15, chest); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Donkey extends ChestedHorse {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Llama extends ChestedHorse { + + public static @NotNull WrappedWatchableObject strength(final int strength) { + requireAtLeast(11); + + return FACTORY.createWatchable(VERSION >= 15 ? 19 : 16, strength); + } + + public static @NotNull WrappedWatchableObject carpetColor(final int carpetColor) { + requireAtLeast(11); + + return FACTORY.createWatchable(VERSION >= 15 ? 20 : 17, carpetColor); + } + + public static @NotNull WrappedWatchableObject variant(final @NonNull LlamaVariant variant) { + requireAtLeast(11); + + return FACTORY.createWatchable(VERSION >= 15 ? 21 : 18, variant.checkedValue()); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum LlamaVariant implements IntValue { + CREAMY(0), + WHITE(1), + BROWN(2), + GRAY(3); + + @Getter int value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class TraderLama {} // yup + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Mule extends ChestedHorse {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Bee extends Animal { + + public static @NotNull WrappedWatchableObject beeFlags(final @NotNull BeeFlag @NonNull ...flags) { + requireAtLeast(15); + + return FACTORY.createWatchable(16, ByteFlag.allChecked(flags)); + } + + public static @NotNull WrappedWatchableObject angryTime(final int angryTime) { + requireAtLeast(15); + + return FACTORY.createWatchable(17, angryTime); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum BeeFlag implements ByteFlag { + ANGRY(VERSION >= 15 ? (byte) 0x02 : UNSUPPORTED), + STUNG(VERSION >= 15 ? (byte) 0x04 : UNSUPPORTED), + HAS_NECTAR(VERSION >= 15 ? (byte) 0x08 : UNSUPPORTED); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Fox extends Animal { + + public static @NotNull WrappedWatchableObject foxType(final @NonNull FoxType type) { + return FACTORY.createWatchable(VERSION >= 15 ? 16 : 15, type.checkedValue()); + } + + public static @NotNull WrappedWatchableObject foxFlags(final @NotNull FoxFlag @NonNull ...flags) { + return FACTORY.createWatchable(VERSION >= 15 ? 17 : 16, ByteFlag.allChecked(flags)); + } + + public static @NotNull WrappedWatchableObject firstUUID(final @Nullable UUID uuid) { + return FACTORY.createWatchableOptional(VERSION >= 15 ? 18 : 17, uuid); + } + + public static @NotNull WrappedWatchableObject secondUUID(final @Nullable UUID uuid) { + return FACTORY.createWatchableOptional(VERSION >= 15 ? 19 : 18, uuid); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum FoxType implements IntValue { + RED(0), + SNOW(1); + + @Getter int value; + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum FoxFlag implements ByteFlag { + SITTING((byte) 0x01), + CROUCHING((byte) 0x04), + INTERESTED((byte) 0x04), + POUNCING((byte) 0x10), + SLEEPING((byte) 0x20), + FACEPLANTED((byte) 0x40), + DEFENDING((byte) 0x80); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Ocelot extends Animal { + + public static @NotNull WrappedWatchableObject trusting(final boolean trusting) { + requireAtLeast(15); + + return FACTORY.createWatchable(16, trusting); + } + + public static @NotNull WrappedWatchableObject variant(final @NonNull OcelotVariant variant) { + requireAtMost(13); + + val value = variant.checkedValue(); + return VERSION >= 9 + ? FACTORY.createWatchable(VERSION >= 10 ? 15 : 14, value) + : FACTORY.createWatchable(18, (byte) value); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum OcelotVariant implements IntValue { + UNTAMED(VERSION >= 13 ? UNSUPPORTED : 0), + TUXEDO(VERSION >= 13 ? UNSUPPORTED : 1), + TABBY(VERSION >= 13 ? UNSUPPORTED : 2), + SIAMESE(VERSION >= 13 ? UNSUPPORTED : 3); + + @Getter int value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Panda extends Animal { + + public static @NotNull WrappedWatchableObject breedTimer(final int breedTimer) { + requireAtLeast(14); + + return FACTORY.createWatchable(VERSION >= 15 ? 16 : 15, breedTimer); + } + + public static @NotNull WrappedWatchableObject sneezeTimer(final int sneezeTimer) { + requireAtLeast(14); + + return FACTORY.createWatchable(VERSION >= 15 ? 17 : 16, sneezeTimer); + } + + public static @NotNull WrappedWatchableObject earTimer(final int earTimer) { + requireAtLeast(14); + + return FACTORY.createWatchable(VERSION >= 15 ? 18 : 17, earTimer); + } + + public static @NotNull WrappedWatchableObject mainGene(final byte mainGene) { + requireAtLeast(14); + + return FACTORY.createWatchable(VERSION >= 15 ? 19 : 18, mainGene); + } + + public static @NotNull WrappedWatchableObject hiddenGene(final byte hiddenGene) { + requireAtLeast(14); + + return FACTORY.createWatchable(VERSION >= 15 ? 20 : 19, hiddenGene); + } + + public static @NotNull WrappedWatchableObject pandaFlags(final @NotNull PandaFlag @NonNull ...flags) { + requireAtLeast(14); + + return FACTORY.createWatchable(VERSION >= 15 ? 21 : 20, ByteFlag.allChecked(flags)); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum PandaFlag implements ByteFlag { + SNEEZING(VERSION >= 14 ? (byte) 0x02 : UNSUPPORTED), + ROLLING(VERSION >= 14 ? (byte) 0x04 : UNSUPPORTED), + SITTING(VERSION >= 14 ? (byte) 0x08 : UNSUPPORTED), + ON_BACK(VERSION >= 14 ? (byte) 0x10 : UNSUPPORTED); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Pig extends Animal { + + public static @NotNull WrappedWatchableObject saddle(final boolean saddle) { + return FACTORY.createWatchable(VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : VERSION >= 9 ? 12 : 16, saddle); + } + + public static @NotNull WrappedWatchableObject boostTime(final int boostTime) { + requireAtLeast(11); + + return FACTORY.createWatchable(VERSION >= 15 ? 17 : 14, boostTime); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Rabbit extends Animal { + + public static @NotNull WrappedWatchableObject type(final int type) { + return VERSION >= 9 + ? FACTORY.createWatchable(VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : 12, type) + : FACTORY.createWatchable(18, (byte) type); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Turtle extends Animal { + + public static @NotNull WrappedWatchableObject home(final @NonNull BlockPosition home) { + requireAtLeast(13); + + return FACTORY.createWatchable(VERSION >= 15 ? 16 : 13, home); + } + + public static @NotNull WrappedWatchableObject hasEgg(final boolean hasEgg) { + requireAtLeast(13); + + return FACTORY.createWatchable(VERSION >= 15 ? 17 : 14, hasEgg); + } + + public static @NotNull WrappedWatchableObject layingEgg(final boolean layingEgg) { + requireAtLeast(13); + + return FACTORY.createWatchable(VERSION >= 15 ? 18 : 15, layingEgg); + } + + public static @NotNull WrappedWatchableObject travelPosition(final @NonNull BlockPosition travelPosition) { + requireAtLeast(13); + + return FACTORY.createWatchable(VERSION >= 15 ? 19 : 16, travelPosition); + } + + public static @NotNull WrappedWatchableObject goingHome(final boolean goingHome) { + requireAtLeast(13); + + return FACTORY.createWatchable(VERSION >= 15 ? 20 : 17, goingHome); + } + + public static @NotNull WrappedWatchableObject travelling(final boolean travelling) { + requireAtLeast(13); + + return FACTORY.createWatchable(VERSION >= 15 ? 21 : 18, travelling); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class PolarBear extends Animal { + + public static @NotNull WrappedWatchableObject standingUp(final boolean standingUp) { + requireAtLeast(10); + + return FACTORY.createWatchable(VERSION >= 15 ? 13 : 16, standingUp); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Chicken extends Animal {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Cow extends Animal {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Hoglin extends Animal { + + public static @NotNull WrappedWatchableObject immuneToZombification(final boolean immuneToZombification) { + requireAtLeast(16); + + return FACTORY.createWatchable(16, immuneToZombification); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Mooshroom extends Cow { + + public static @NotNull WrappedWatchableObject type(final @NonNull String type) { + requireAtLeast(15); + + return FACTORY.createWatchable(16, type); + } + + public static @NotNull WrappedWatchableObject type(final @NonNull MooshroomType type) { + requireAtLeast(15); + + return FACTORY.createWatchable(16, type.checkedValue()); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum MooshroomType implements StringValue { + RED("red"), + BROWN("brown"); + + @Getter @NotNull String value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Sheep extends Animal { + + public static @NotNull WrappedWatchableObject sheepData(final @NonNull DyeColor color, final boolean sheared) { + return FACTORY.createWatchable( + VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : VERSION >= 9 ? 12 : 16, + (byte) (woolId(color) | (sheared ? 0x10 : 0x00)) + ); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Strider extends Animal { + + public static @NotNull WrappedWatchableObject boostTime(final int boostTime) { + requireAtLeast(16); + + return FACTORY.createWatchable(16, boostTime); + } + + public static @NotNull WrappedWatchableObject shaking(final boolean shaking) { + requireAtLeast(16); + + return FACTORY.createWatchable(17, shaking); + } + + public static @NotNull WrappedWatchableObject hasSaddle(final boolean hasSaddle) { + requireAtLeast(16); + + return FACTORY.createWatchable(18, hasSaddle); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class TameableAnimal extends Animal { + + public static @NotNull WrappedWatchableObject tameableAnimalFlags( + final @NotNull TameableAnimalFlag @NonNull ...flags + ) { + return FACTORY.createWatchable( + VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : VERSION >= 9 ? 12 : 16, + ByteFlag.allChecked(flags) + ); + } + + public static @NotNull WrappedWatchableObject owner(final @Nullable UUID ownerUuid) { + return VERSION >= 9 + ? FACTORY.createWatchableOptional(VERSION >= 15 ? 17 : VERSION >= 10 ? 14 : 13, ownerUuid) + : FACTORY.createWatchable( + 17, ownerUuid == null ? "" : Bukkit.getOfflinePlayer(ownerUuid).getName() + ); + } + + public static @NotNull WrappedWatchableObject owner(final @Nullable OfflinePlayer owner) { + return VERSION >= 9 ? FACTORY.createWatchableOptional( + VERSION >= 15 ? 17 : VERSION >= 10 ? 14 : 13, + owner == null ? null : owner.getUniqueId() + ) : FACTORY.createWatchable(17, owner == null ? null : owner.getName()); + } + + public static @NotNull WrappedWatchableObject owner(final @Nullable String ownerName) { + return VERSION >= 9 ? FACTORY.createWatchableOptional( + VERSION >= 15 ? 17 : VERSION >= 10 ? 14 : 13, + uuidByName(ownerName) + ) : FACTORY.createWatchable(21, ownerName == null ? "" : ownerName); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum TameableAnimalFlag implements ByteFlag { + SITTING(VERSION >= 9 ? (byte) 0x01 : UNSUPPORTED), + ANGRY(VERSION >= 16 ? UNSUPPORTED : VERSION >= 9 ? (byte) 0x02 : UNSUPPORTED), + TAMED(VERSION >= 9 ? (byte) 0x04 : UNSUPPORTED); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Cat extends TameableAnimal { + + public static @NotNull WrappedWatchableObject catVariant(final @NonNull CatVariant variant) { + requireAtLeast(14); + + return FACTORY.createWatchable(VERSION >= 15 ? 18 : 17, variant.checkedValue()); + } + + public static @NotNull WrappedWatchableObject lying(final boolean lying) { + requireAtLeast(14); + + return FACTORY.createWatchable(VERSION >= 15 ? 19 : 18, lying); + } + + public static @NotNull WrappedWatchableObject relaxed(final boolean relaxed) { + requireAtLeast(14); + + return FACTORY.createWatchable(VERSION >= 15 ? 20 : 19, relaxed); + } + + public static @NotNull WrappedWatchableObject collarColor(final @NonNull DyeColor collarColor) { + requireAtLeast(14); + + return FACTORY.createWatchable(VERSION >= 15 ? 21 : 20, (int) woolId(collarColor)); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum CatVariant implements IntValue { + TABBY(0), + BLACK(1), + RED(2), + SIAMESE(3), + BRITISH_SHORTHAIR(4), + CALICO(5), + PERSIAN(6), + RAGDOLL(7), + WHITE(8), + JELLIE(VERSION >= 16 ? 9 : UNSUPPORTED), + ALL_BLACK(VERSION >= 16 ? 10 : 9); + + @Getter int value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Wolf extends TameableAnimal { + + public static @NotNull WrappedWatchableObject begging(final boolean begging) { + return FACTORY.createWatchable(VERSION >= 16 ? 18 : VERSION >= 10 ? 16 : VERSION >= 9 ? 15 : 19, begging); + } + + public static @NotNull WrappedWatchableObject collarColor(final @NonNull DyeColor collarColor) { + val value = woolId(collarColor); + return VERSION >= 9 + ? FACTORY.createWatchable(VERSION >= 16 ? 19 : VERSION >= 10 ? 17 : 16, (int) value) + : FACTORY.createWatchable(20, value); + } + + public static @NotNull WrappedWatchableObject damageTaken(final float damageTaken) { + requireAtMost(14); + + return FACTORY.createWatchable(VERSION >= 10 ? 15 : VERSION >= 9 ? 14 : 18, damageTaken); + } + + public static @NotNull WrappedWatchableObject angerTime(final int angerTime) { + requireAtLeast(16); + + return FACTORY.createWatchable(20, angerTime); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Parrot extends TameableAnimal { + + public static @NotNull WrappedWatchableObject variant(final @NonNull Variant variant) { + requireAtLeast(12); + + return FACTORY.createWatchable(VERSION >= 15 ? 18 : 15, variant.checkedValue()); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum Variant implements IntValue { + RED_BLUE(0), + BLUE(1), + GREEN(2), + YELLOW_BLUE(3), + GREY(4); + + @Getter int value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class AbstractVillager extends AgeableMob { + + public static @NotNull WrappedWatchableObject headShakeTimer(final int headShakeTimer) { + requireAtLeast(16); + + return FACTORY.createWatchable(16, headShakeTimer); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Villager extends AbstractVillager { + + public static @NotNull WrappedWatchableObject profession(final @NonNull VillagerProfession profession) { + requireAtMost(13); + + return FACTORY.createWatchable(VERSION >= 10 ? 13 : VERSION >= 9 ? 12 : 16, profession.checkedValue()); + } + + public static @NotNull WrappedWatchableObject villagerData(final @NonNull WrappedVillagerData villagerData) { + requireAtLeast(14); + + return FACTORY.createWatchable(VERSION >= 15 ? 17 : 16, villagerData); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum VillagerProfession implements IntValue { + FARMER(VERSION >= 14 ? UNSUPPORTED : 0), + LIBRARIAN(VERSION >= 14 ? UNSUPPORTED : 1), + PRIEST(VERSION >= 14 ? UNSUPPORTED : 2), + BLACKSMITH(VERSION >= 14 ? UNSUPPORTED : 3), + SILVER(VERSION >= 14 ? UNSUPPORTED : 4); + + @Getter int value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class WanderingTrader extends AbstractVillager {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class AbstractGolem extends PathfinderMob {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class IronGolem extends AbstractGolem { + + public static @NotNull WrappedWatchableObject ironGolemFlags(final @NotNull IronGolemFlag @NonNull ...flags) { + return FACTORY.createWatchable( + VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : VERSION >= 9 ? 11 : 16, + ByteFlag.allChecked(flags) + ); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @SuppressWarnings("Singleton") // there just is single entry in this enum + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum IronGolemFlag implements ByteFlag { + PLAYER_CREATED((byte) 0x01); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class SnowGolem extends AbstractGolem { + + public static @NotNull WrappedWatchableObject snowGolemFlags(final @NotNull SnowGolemFlag @NonNull ...flags) { + return FACTORY.createWatchable( + VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : VERSION >= 9 ? 10 : 15 /* missing spec for this */, + ByteFlag.allChecked(flags) + ); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @SuppressWarnings("Singleton") // there just is single entry in this enum + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum SnowGolemFlag implements ByteFlag { + PLAYER_CREATED((byte) 0x10); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Shulker extends AbstractGolem { + + public static @NotNull WrappedWatchableObject attachFace(final @NonNull EnumWrappers.Direction direction) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : 11, direction); + } + + public static @NotNull WrappedWatchableObject attachmentPosition( + final @Nullable BlockPosition attachmentPosition + ) { + requireAtLeast(9); + + return FACTORY.createWatchableOptional(VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : 12, attachmentPosition); + } + + public static @NotNull WrappedWatchableObject shieldHeight(final byte shieldHeight) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 17 : VERSION >= 10 ? 14 : 13, shieldHeight); + } + + public static @NotNull WrappedWatchableObject color(final @NonNull DyeColor dyeColor) { + requireAtLeast(11); + + return FACTORY.createWatchable(VERSION >= 15 ? 18 : 15, woolId(dyeColor)); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Monster extends PathfinderMob {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class BasePiglin extends PathfinderMob { + + public static @NotNull WrappedWatchableObject immuneToZombification(final boolean immuneToZombification) { + requireAtLeast(16); + + return FACTORY.createWatchable(15, immuneToZombification); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Piglin extends BasePiglin { + + public static @NotNull WrappedWatchableObject baby(final boolean baby) { + requireAtLeast(16); + + return FACTORY.createWatchable(16, baby); + } + + public static @NotNull WrappedWatchableObject chargingCrossbow(final boolean chargingCrossbow) { + requireAtLeast(16); + + return FACTORY.createWatchable(17, chargingCrossbow); + } + + public static @NotNull WrappedWatchableObject dancing(final boolean dancing) { + requireAtLeast(16); + + return FACTORY.createWatchable(18, dancing); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class PiglinBrute extends BasePiglin {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Blaze extends Monster { + + public static @NotNull WrappedWatchableObject blazeFlags(final @NotNull BlazeFlag @NonNull ...flags) { + return FACTORY.createWatchable( + VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : VERSION >= 9 ? 11 : 16, + ByteFlag.allChecked(flags) + ); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @SuppressWarnings("Singleton") // there just is single entry in this enum + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum BlazeFlag implements ByteFlag { + ON_FIRE((byte) 0x01); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Creeper extends Monster { + + public static @NotNull WrappedWatchableObject creeperState(final @NonNull CreeperState state) { + return FACTORY.createWatchable( + VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : VERSION >= 9 ? 11 : 16, + state.checkedValue() + ); + } + + public static @NotNull WrappedWatchableObject charged(final boolean charged) { + return FACTORY.createWatchable(VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : VERSION >= 9 ? 12 : 17, charged); + } + + public static @NotNull WrappedWatchableObject ignited(final boolean ignited) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 17 : VERSION >= 10 ? 14 : 13, ignited); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum CreeperState implements IntValue { + IDLE(-1), + FUSE(1); + + @Getter int value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Endermite extends Monster {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Giant extends Monster {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Guardian extends Monster { + + public static @NotNull WrappedWatchableObject retractingSpikes(final boolean retractingSpikes) { + return VERSION >= 11 + ? FACTORY.createWatchable(VERSION >= 15 ? 15 : 12, retractingSpikes) + : FACTORY.createWatchable( + VERSION >= 10 ? 12 : VERSION >= 9 ? 11 : 16, + GuardianFlag.RETRACTING_SPIKES.checkedValue() + ); + } + + public static @NotNull WrappedWatchableObject guardianFlags(final @NonNull GuardianFlag... guardianFlags) { + requireAtMost(10); + + return FACTORY.createWatchable( + VERSION >= 10 ? 12 : VERSION >= 9 ? 11 : 16, + ByteFlag.allChecked(guardianFlags) + ); + } + + public static @NotNull WrappedWatchableObject targetEntity(final int targetEntityId) { + return FACTORY.createWatchable( + VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : VERSION >= 9 ? 12 : 17, + targetEntityId + ); + } + + public static @NotNull WrappedWatchableObject targetEntity(final @NonNull org.bukkit.entity.Entity entity) { + return FACTORY.createWatchable( + VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : VERSION >= 9 ? 12 : 17, + entity.getEntityId() + ); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum GuardianFlag implements ByteFlag { + ELDERY(VERSION >= 11 ? UNSUPPORTED : (byte) 0x02), + RETRACTING_SPIKES((byte) 0x04); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class ElderGuardian extends Guardian {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Silverfish extends Monster {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Raider extends Monster { + + public static @NotNull WrappedWatchableObject celebrating(final boolean celebrating) { + requireAtLeast(14); + + return FACTORY.createWatchable(VERSION >= 15 ? 15 : 14, celebrating); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class AbstractIllager extends Raider { + + public static @NotNull WrappedWatchableObject abstractIllagerFlags( + final @NotNull AbstractIllagerFlag @NonNull ...flags + ) { + requireAtLeast(12); + requireAtMost(13); + + return FACTORY.createWatchable(12, ByteFlag.allChecked(flags)); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @SuppressWarnings("Singleton") // there just is single entry in this enum + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum AbstractIllagerFlag implements ByteFlag { + HAS_TARGET(VERSION >= 14 ? UNSUPPORTED : (byte) 0x01); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Vindicator extends AbstractIllager {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Pillager extends AbstractIllager { + + public static @NotNull WrappedWatchableObject charging(final boolean charging) { + requireAtLeast(16); + + // note: missing spec for 1.15 + return FACTORY.createWatchable(VERSION >= 15 ? 16 : 15, charging); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class SpellcasterIllager extends AbstractIllager { + + public static @NotNull WrappedWatchableObject spell(final @NonNull Spell spell) { + return FACTORY.createWatchable(13, spell.checkedValue()); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum Spell implements ByteValue { + NONE(VERSION >= 12 ? (byte) 0 : UNSUPPORTED), + SUMMON_VEX(VERSION >= 12 ? (byte) 1 : UNSUPPORTED), + ATTACK(VERSION >= 12 ? (byte) 2 : UNSUPPORTED), + WOLOLO(VERSION >= 12 ? (byte) 3 : UNSUPPORTED), + DISAPPEAR(VERSION >= 13 ? (byte) 5 : UNSUPPORTED), + BLINDNESS(VERSION >= 13 ? (byte) 6 : UNSUPPORTED); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Evoker extends SpellcasterIllager {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Illusioner extends SpellcasterIllager {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Ravager extends Raider {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Witch extends Raider { + + public static @NotNull WrappedWatchableObject drinkingPotion(final boolean drinkingPotion) { + return FACTORY.createWatchable( + VERSION >= 15 ? 16 : VERSION >= 14 ? 15 : VERSION >= 10 ? 12 : VERSION >= 9 ? 11 : 21, + drinkingPotion + ); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class EvokerFangs extends Entity {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Vex extends Monster { + + public static @NotNull WrappedWatchableObject vexFlags(final @NotNull VexFlag @NonNull ...flags) { + requireAtLeast(11); + + return FACTORY.createWatchable(VERSION >= 15 ? 15 : 12, ByteFlag.allChecked(flags)); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @SuppressWarnings("Singleton") // there just is single entry in this enum + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum VexFlag implements ByteFlag { + ATTACKING(VERSION >= 11 ? (byte) 0x01 : UNSUPPORTED); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class AbstractSkeleton extends Monster { + + public static @NotNull WrappedWatchableObject skeletonType(final @NonNull SkeletonType type) { + requireAtMost(10); + + return FACTORY.createWatchable(VERSION >= 10 ? 13 : VERSION >= 9 ? 11 : 13, type.checkedValue()); + } + + public static @NotNull WrappedWatchableObject swingingArms(final boolean swingingArms) { + requireAtLeast(9); + requireAtMost(13); + + return FACTORY.createWatchable(VERSION >= 11 ? 12 : VERSION >= 10 ? 13 : 12, swingingArms); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum SkeletonType implements ByteValue { + NORMAL(VERSION >= 11 ? UNSUPPORTED : (byte) 0), + WITHER(VERSION >= 11 ? UNSUPPORTED : (byte) 1); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Skeleton extends AbstractSkeleton {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class WitherSkeleton extends AbstractSkeleton {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Stray extends AbstractSkeleton {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Spider extends Monster { + + public static @NotNull WrappedWatchableObject spiderFlags(final @NotNull SpiderFlag @NonNull ...flags) { + return FACTORY.createWatchable( + VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : VERSION >= 9 ? 11 : 16, + ByteFlag.allChecked(flags) + ); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @SuppressWarnings("Singleton") // there just is single entry in this enum + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum SpiderFlag implements ByteFlag { + CLIMBING((byte) 0x01); + + @Getter byte value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Wither extends Monster { + + public static @NotNull WrappedWatchableObject centerHeadTarget(final int centerHeadTargetId) { + return FACTORY.createWatchable( + VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : VERSION >= 9 ? 11 : 17, + centerHeadTargetId + ); + } + + public static @NotNull WrappedWatchableObject centerHeadTarget( + final @Nullable org.bukkit.entity.Entity centerHeadTarget + ) { + return FACTORY.createWatchable( + VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : VERSION >= 9 ? 11 : 17, + centerHeadTarget == null ? 0 : centerHeadTarget.getEntityId() + ); + } + + public static @NotNull WrappedWatchableObject leftHeadTarget(final int leftHeadTargetId) { + return FACTORY.createWatchable( + VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : VERSION >= 9 ? 12 : 18, + leftHeadTargetId + ); + } + + public static @NotNull WrappedWatchableObject leftHeadTarget( + final @NonNull org.bukkit.entity.Entity leftHeadTarget + ) { + return FACTORY.createWatchable( + VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : VERSION >= 9 ? 12 : 18, + leftHeadTarget.getEntityId() + ); + } + + public static @NotNull WrappedWatchableObject rightHeadTarget(final int rightHeadTargetId) { + return FACTORY.createWatchable( + VERSION >= 15 ? 17 : VERSION >= 10 ? 14 : VERSION >= 9 ? 13 : 19, + rightHeadTargetId + ); + } + + public static @NotNull WrappedWatchableObject rightHeadTarget( + final @NonNull org.bukkit.entity.Entity rightHeadTarget + ) { + return FACTORY.createWatchable( + VERSION >= 15 ? 17 : VERSION >= 10 ? 14 : VERSION >= 9 ? 13 : 19, + rightHeadTarget.getEntityId() + ); + } + + public static @NotNull WrappedWatchableObject invulnerableTime(final int invulnerableTime) { + return FACTORY.createWatchable( + VERSION >= 15 ? 18 : VERSION >= 10 ? 15 : VERSION >= 9 ? 14 : 20, + invulnerableTime + ); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Zoglin extends Monster { + + public static @NotNull WrappedWatchableObject baby(final boolean baby) { + requireAtLeast(16); + + return FACTORY.createWatchable(15, baby); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Zombie extends Monster { + + public static @NotNull WrappedWatchableObject baby(final boolean baby) { + return FACTORY.createWatchable(VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : VERSION >= 9 ? 11 : 12, baby); + } + + public static @NotNull WrappedWatchableObject zombieVillager(final boolean zombieVillager) { + requireAtMost(8); + + return FACTORY.createWatchable(13, zombieVillager); + } + + public static @NotNull WrappedWatchableObject zombieType(final int zombieType) { + requireAtLeast(9); + requireAtMost(10); + + return FACTORY.createWatchable(VERSION >= 10 ? 13 : 12, zombieType); + } + + public static @NotNull WrappedWatchableObject converting(final boolean converting) { + requireAtMost(10); + + return FACTORY.createWatchable(VERSION >= 10 ? 14 : VERSION >= 9 ? 13 : 14, converting); + } + + public static @NotNull WrappedWatchableObject handsUp(final boolean handsUp) { + requireAtLeast(9); + requireAtMost(14); + + return FACTORY.createWatchable(VERSION >= 11 ? 14 : VERSION >= 10 ? 15 : 14, handsUp); + } + + public static @NotNull WrappedWatchableObject becomingDrowned(final boolean becomingDrowned) { + requireAtLeast(13); + + return FACTORY.createWatchable(VERSION >= 15 ? 17 : 15, becomingDrowned); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class ZombieVillager extends Zombie { + + public static @NotNull WrappedWatchableObject converting(final boolean converting) { + return FACTORY.createWatchable( + VERSION >= 15 ? 18 : VERSION >= 13 ? 16 : VERSION >= 11 + ? 15 : VERSION >= 10 ? 14 : VERSION >= 9 ? 13 : 14, + converting + ); + } + + public static @NotNull WrappedWatchableObject profession(final int profession) { + requireAtLeast(11); + requireAtMost(14); + + return FACTORY.createWatchable(VERSION >= 13 ? 17 : 16, profession); + } + + public static @NotNull WrappedWatchableObject villagerData(final @NonNull WrappedVillagerData villagerData) { + requireAtLeast(15); + + return FACTORY.createWatchable(19, villagerData); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Husk extends Zombie {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Drowned extends Zombie {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class ZombifiedPiglin extends Zombie {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Enderman extends Monster { + + public static @NotNull WrappedWatchableObject carriedBlockId(final short carriedBlockId) { + requireAtMost(8); + + return FACTORY.createWatchable(16, carriedBlockId); + } + + public static @NotNull WrappedWatchableObject carriedBlockData(final byte carriedBlockData) { + requireAtMost(8); + + return FACTORY.createWatchable(17, carriedBlockData); + } + + public static @NotNull WrappedWatchableObject carriedBlock(final @Nullable WrappedBlockData carriedBlock) { + requireAtLeast(9); + + return FACTORY.createWatchableOptional(VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : 11, carriedBlock); + } + + public static @NotNull WrappedWatchableObject screaming(final boolean screaming) { + return FACTORY.createWatchable(VERSION >= 15 ? 16 : VERSION >= 10 ? 13 : 12, screaming); + } + + public static @NotNull WrappedWatchableObject staring(final boolean staring) { + requireAtLeast(15); + + return FACTORY.createWatchable(17, staring); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class EnderDragon extends Mob { + + public static @NotNull WrappedWatchableObject phase(final @NonNull EnderDragonPhase phase) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : 11, phase.checkedValue()); + } + + @RequiredArgsConstructor + @Accessors(fluent = true) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + public enum EnderDragonPhase implements IntValue { + CIRCLING(VERSION >= 9 ? 0 : UNSUPPORTED), + STRAFING(VERSION >= 9 ? 1 : UNSUPPORTED), + FLYING_TO_LAND(VERSION >= 9 ? 2 : UNSUPPORTED), + LANDING(VERSION >= 9 ? 3 : UNSUPPORTED), + TAKING_OFF(VERSION >= 9 ? 4 : UNSUPPORTED), + BREATHING(VERSION >= 9 ? 5 : UNSUPPORTED), + LOOKING_FOR_PLAYER(VERSION >= 9 ? 6 : UNSUPPORTED), + ROARING(VERSION >= 9 ? 7 : UNSUPPORTED), + CHARGING_PLAYER(VERSION >= 9 ? 8 : UNSUPPORTED), + FLYING_TO_DIE(VERSION >= 9 ? 9 : UNSUPPORTED), + NO_AI(VERSION >= 9 ? 10 : UNSUPPORTED); + + @Getter int value; + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Flying extends Mob {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Ghast extends Flying { + + public static @NotNull WrappedWatchableObject attacking(final boolean attacking) { + return FACTORY.createWatchable(VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : VERSION >= 9 ? 11 : 16, attacking); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Phantom extends Flying { + + public static @NotNull WrappedWatchableObject size(final int size) { + requireAtLeast(13); + + return FACTORY.createWatchable(VERSION >= 15 ? 15 : 12, size); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Slime extends Mob { + + public static @NotNull WrappedWatchableObject size(final int size) { + return FACTORY.createWatchable(VERSION >= 15 ? 15 : VERSION >= 10 ? 12 : VERSION >= 9 ? 12 : 16, size); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class LlamaSpit extends Entity {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class AbstractMinecart extends Entity { + + private static final int OFFSET = VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : VERSION >= 9 ? 5 : 17; + private static final int MAX_INDEX = OFFSET + 5; + + public static @NotNull WrappedWatchableObject shakingPower(final int shakingPower) { + return FACTORY.createWatchable(OFFSET, shakingPower); + } + + public static @NotNull WrappedWatchableObject shakingDirection(final int shakingDirection) { + return FACTORY.createWatchable(OFFSET + 1, shakingDirection); + } + + public static @NotNull WrappedWatchableObject shakingMultiplier(final int shakingMultiplier) { + return FACTORY.createWatchable(OFFSET + 2, shakingMultiplier); + } + + public static @NotNull WrappedWatchableObject customBlockIdAndDamage(final int customBlockIdAndDamage) { + return FACTORY.createWatchable(OFFSET + 3, customBlockIdAndDamage); + } + + public static @NotNull WrappedWatchableObject customBlockY(final int customBlockY) { + return FACTORY.createWatchable(OFFSET + 4, customBlockY); + } + + public static @NotNull WrappedWatchableObject showCustomBlock(final boolean showCustomBlock) { + return FACTORY.createWatchable(OFFSET + 5, showCustomBlock); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Minecart extends AbstractMinecart {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class AbstractMinecartContainer extends AbstractMinecart {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class MinecartHopper extends AbstractMinecartContainer {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class MinecartChest extends AbstractMinecartContainer {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class MinecartFurnace extends AbstractMinecart { + + private static final int OFFSET = AbstractMinecart.MAX_INDEX + 1; + private static final int MAX_INDEX = OFFSET; + + public static @NotNull WrappedWatchableObject powered(final boolean powered) { + return FACTORY.createWatchable(MAX_INDEX, powered); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class MinecartTnt extends AbstractMinecart {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class MinecartSpawner extends AbstractMinecart {} + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class MinecartCommandBlock extends AbstractMinecart { + + private static final int OFFSET = AbstractMinecart.MAX_INDEX + 1; + private static final int MAX_INDEX = OFFSET + 1; + + public static @NotNull WrappedWatchableObject command(final @NonNull String command) { + requireAtLeast(9); + + return FACTORY.createWatchable(OFFSET, command); + } + + public static @NotNull WrappedWatchableObject lastOutput(final @NonNull WrappedChatComponent lastOutput) { + requireAtLeast(9); + + return FACTORY.createWatchable(MAX_INDEX, lastOutput); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class TNTPrimed extends Entity { + + public static @NotNull WrappedWatchableObject fuseTime(final int fuseTime) { + requireAtLeast(9); + + return FACTORY.createWatchable(VERSION >= 15 ? 7 : VERSION >= 10 ? 6 : 5, fuseTime); + } + } + + @SuppressWarnings("deprecation") + private static byte woolId(final @NotNull DyeColor color) { + return color.getWoolData(); + } + + @SuppressWarnings("deprecation") + @Contract("null -> null") + private static @Nullable OfflinePlayer offlinePlayerByName(final @Nullable String name) { + return name == null ? null : Bukkit.getOfflinePlayer(name); + } + + @Contract("null -> null") + private static @Nullable UUID uuidByName(final @Nullable String name) { + final OfflinePlayer player; + return (player = offlinePlayerByName(name)) == null ? null : player.getUniqueId(); + } + + @UtilityClass + private static final @NonNull class EmptyChatComponent { + private final @NotNull WrappedChatComponent INSTANCE = WrappedChatComponent.fromJson("{\"text\":\"\"}"); + } + + @FunctionalInterface + private interface ByteValue { + byte UNSUPPORTED = -1; + + byte value(); + + default byte checkedValue() { + final byte value; + if ((value = value()) == UNSUPPORTED) throw new UnsupportedOperationException( + "Value " + this + " is not supported on version 1." + VERSION + ); + + return value; + } + } + + @FunctionalInterface + private interface IntValue { + int UNSUPPORTED = -1; + + int value(); + + default int checkedValue() { + final int value; + if ((value = value()) == UNSUPPORTED) throw new UnsupportedOperationException( + "Value " + this + " is not supported on version 1." + VERSION + ); + + return value; + } + } + + @FunctionalInterface + private interface StringValue { + String UNSUPPORTED = null; + + @Nullable String value(); + + default @NotNull String checkedValue() { + final String value; + if ((value = value()) == null /* == UNSUPPORTED */) throw new UnsupportedOperationException( + "Value " + this + " is not supported on version 1." + VERSION + ); + + return value; + } + } + + @FunctionalInterface + private interface ByteFlag extends ByteValue { + + // No need for explicit null-check as the interface is private + static byte allChecked(final @NotNull ByteFlag @NotNull ... flags) { + byte value = 0; + for (val flag : flags) value |= flag.value(); + + return value; + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/metadata/StandardDataWatcherFactory.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/metadata/StandardDataWatcherFactory.java new file mode 100644 index 000000000..014a053e1 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/metadata/StandardDataWatcherFactory.java @@ -0,0 +1,518 @@ +package ru.progrm_jarvis.minecraft.commons.nms.metadata; + +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.*; +import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry; +import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.UUID; + +/** + * DataWatcher factory for post 1.9 versions. + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class StandardDataWatcherFactory implements DataWatcherFactory { + + private static final @NotNull WrappedDataWatcher.Serializer BYTE_SERIALIZER + = Registry.get(Byte.class); + private static final @NotNull WrappedDataWatcher.Serializer INT_SERIALIZER + = Registry.get(Integer.class); + private static final @NotNull WrappedDataWatcher.Serializer FLOAT_SERIALIZER + = Registry.get(Float.class); + private static final @NotNull WrappedDataWatcher.Serializer STRING_SERIALIZER + = Registry.get(String.class); + private static final @NotNull WrappedDataWatcher.Serializer I_CHAT_BASE_COMPONENT_SERIALIZER + = Registry.getChatComponentSerializer(); + private static final @NotNull WrappedDataWatcher.Serializer ITEM_STACK_SERIALIZER + = Registry.getItemStackSerializer(false); + private static final @NotNull WrappedDataWatcher.Serializer OPTIONAL_I_BLOCK_DATA_SERIALIZER + = Registry.getBlockDataSerializer(true); + private static final @NotNull WrappedDataWatcher.Serializer BOOLEAN_SERIALIZER + = Registry.get(Boolean.class); + private static final @NotNull WrappedDataWatcher.Serializer VECTOR_3F_SERIALIZER + = Registry.getVectorSerializer(); + private static final @NotNull WrappedDataWatcher.Serializer BLOCK_POSITION_SERIALIZER + = Registry.getBlockPositionSerializer(false); + private static final @NotNull WrappedDataWatcher.Serializer OPTIONAL_BLOCK_POSITION_SERIALIZER + = Registry.getBlockPositionSerializer(true); + private static final @NotNull WrappedDataWatcher.Serializer ENUM_DIRECTION_SERIALIZER + = Registry.getDirectionSerializer(); + private static final @NotNull WrappedDataWatcher.Serializer OPTIONAL_UUID_SERIALIZER + = Registry.getUUIDSerializer(true); + private static final @NotNull WrappedDataWatcher.Serializer NBT_TAG_COMPOUND_SERIALIZER + = Registry.getNBTCompoundSerializer(); + // Previously unsupported types + private static final @Nullable WrappedDataWatcher.Serializer OPTIONAL_I_CHAT_BASE_COMPONENT_SERIALIZER; + private static final @Nullable WrappedDataWatcher.Serializer PARTICLE_SERIALIZER; + private static final @Nullable WrappedDataWatcher.Serializer VILLAGER_DATA_SERIALIZER; + private static final @Nullable WrappedDataWatcher.Serializer ENTITY_POSE_SERIALIZER; + private static final @Nullable WrappedDataWatcher.Serializer OPTIONAL_INT_SERIALIZER; + + // Unsupported types + private static final @Nullable WrappedDataWatcher.Serializer SHORT_SERIALIZER; + + static { + WrappedDataWatcher.Serializer serializer; + + try { + serializer = Registry.get(Integer.class, true); + } catch (final IllegalArgumentException e) { + serializer = null; + } + OPTIONAL_INT_SERIALIZER = serializer; + + try { + serializer = Registry.getChatComponentSerializer(true); + } catch (final IllegalArgumentException e) { + serializer = null; + } + OPTIONAL_I_CHAT_BASE_COMPONENT_SERIALIZER = serializer; + + { + final Class nmsClass; + if ((nmsClass = MinecraftReflection.getParticleTypeClass()) != null) try { + serializer = Registry.get(nmsClass); + } catch (final IllegalArgumentException e) { + serializer = null; + } else serializer = null; + } + PARTICLE_SERIALIZER = serializer; + + { + final Class nmsClass; + if ((nmsClass = WrappedVillagerData.getNmsClass()) != null) try { + serializer = Registry.get(nmsClass); + } catch (final IllegalArgumentException e) { + serializer = null; + } else serializer = null; + } + VILLAGER_DATA_SERIALIZER = serializer; + + { + final Class nmsClass; + if ((nmsClass = EnumWrappers.getEntityPoseClass()) != null) try { + serializer = Registry.get(nmsClass); + } catch (final IllegalArgumentException e) { + serializer = null; + } else serializer = null; + } + ENTITY_POSE_SERIALIZER = serializer; + + try { + serializer = Registry.get(Short.class); + } catch (final IllegalArgumentException e) { + serializer = null; + } + SHORT_SERIALIZER = serializer; + } + + public static @NotNull DataWatcherFactory create() { + return new StandardDataWatcherFactory(); + } + + @Override + public @NotNull DataWatcherModifier modifier(WrappedDataWatcher watcher) { + return new StandardDataWatcherModifier(new WrappedDataWatcher()); + } + + @Override + public @NotNull DataWatcherModifier modifier() { + return new StandardDataWatcherModifier(new WrappedDataWatcher()); + } + + // Actual types + + private @NotNull WrappedDataWatcherObject watcherObjectByte(final int id) { + return new WrappedDataWatcherObject(id, BYTE_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectInt(final int id) { + return new WrappedDataWatcherObject(id, INT_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectFloat(final int id) { + return new WrappedDataWatcherObject(id, FLOAT_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectString(final int id) { + return new WrappedDataWatcherObject(id, STRING_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectIChatBaseComponent(final int id) { + return new WrappedDataWatcherObject(id, I_CHAT_BASE_COMPONENT_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectOptionalIChatBaseComponent(final int id) { + if (OPTIONAL_I_CHAT_BASE_COMPONENT_SERIALIZER == null) throw new UnsupportedOperationException( + "Serialization of `Optional` is unavailable" + ); + + return new WrappedDataWatcherObject(id, OPTIONAL_I_CHAT_BASE_COMPONENT_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectItemStack(final int id) { + return new WrappedDataWatcherObject(id, ITEM_STACK_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectBoolean(final int id) { + return new WrappedDataWatcherObject(id, BOOLEAN_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectVector3f(final int id) { + return new WrappedDataWatcherObject(id, VECTOR_3F_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectBlockPosition(final int id) { + return new WrappedDataWatcherObject(id, BLOCK_POSITION_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectOptionalBlockPosition(final int id) { + return new WrappedDataWatcherObject(id, OPTIONAL_BLOCK_POSITION_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectEnumDirection(final int id) { + return new WrappedDataWatcherObject(id, ENUM_DIRECTION_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectOptionalUUID(final int id) { + return new WrappedDataWatcherObject(id, OPTIONAL_UUID_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectOptionalIBlockData(final int id) { + return new WrappedDataWatcherObject(id, OPTIONAL_I_BLOCK_DATA_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectNBTTagCompound(final int id) { + return new WrappedDataWatcherObject(id, NBT_TAG_COMPOUND_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectParticle(final int id) { + if (PARTICLE_SERIALIZER == null) throw new UnsupportedOperationException( + "Serialization of `Particle` is unavailable" + ); + + return new WrappedDataWatcherObject(id, PARTICLE_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectVillagerData(final int id) { + if (VILLAGER_DATA_SERIALIZER == null) throw new UnsupportedOperationException( + "Serialization of `VillagerData` is unavailable" + ); + + return new WrappedDataWatcherObject(id, VILLAGER_DATA_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectOptionalInt(final int id) { + if (OPTIONAL_INT_SERIALIZER == null) throw new UnsupportedOperationException( + "Serialization of `Optional` is unavailable" + ); + + return new WrappedDataWatcherObject(id, OPTIONAL_INT_SERIALIZER); + } + + private @NotNull WrappedDataWatcherObject watcherObjectEntityPose(final int id) { + if (ENTITY_POSE_SERIALIZER == null) throw new UnsupportedOperationException( + "Serialization of `EntityPose` is unavailable" + ); + + return new WrappedDataWatcherObject(id, ENTITY_POSE_SERIALIZER); + } + + // Unsupported types + + private @NotNull WrappedDataWatcherObject watcherObjectShort(final int id) { + if (SHORT_SERIALIZER == null) throw new UnsupportedOperationException( + "Serialization of `Short` is unavailable" + ); + + return new WrappedDataWatcherObject(id, SHORT_SERIALIZER); + } + + /////////////////////////////////////////////////////////////////////////// + // #createWatchableObject(id, value) + /////////////////////////////////////////////////////////////////////////// + + @Override + public @NotNull WrappedWatchableObject createWatchable(final int id, final byte value) { + return new WrappedWatchableObject(watcherObjectByte(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchable(final int id, final int value) { + return new WrappedWatchableObject(watcherObjectInt(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchable(final int id, final float value) { + return new WrappedWatchableObject(watcherObjectFloat(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchable(final int id, final String value) { + return new WrappedWatchableObject(watcherObjectString(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableIChatBaseComponent(final int id, + final @NonNull Object value) { + return new WrappedWatchableObject(watcherObjectIChatBaseComponent(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableOptionalIChatBaseComponent(final int id, + final @Nullable Object value) { + return new WrappedWatchableObject(watcherObjectOptionalIChatBaseComponent(id), Optional.ofNullable(value)); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableItemStack(final int id, final Object value) { + return new WrappedWatchableObject(watcherObjectItemStack(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchable(final int id, final boolean value) { + return new WrappedWatchableObject(watcherObjectBoolean(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableVector3f(final int id, final @NotNull Object value) { + return new WrappedWatchableObject(watcherObjectVector3f(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableBlockPosition(final int id, final @NotNull Object value) { + return new WrappedWatchableObject(watcherObjectBlockPosition(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableOptionalBlockPosition(final int id, + final @Nullable Object value) { + return new WrappedWatchableObject(watcherObjectOptionalBlockPosition(id), Optional.ofNullable(value)); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableEnumDirection(final int id, final @NonNull Object value) { + return new WrappedWatchableObject(watcherObjectEnumDirection(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableOptional(final int id, final @Nullable UUID value) { + return new WrappedWatchableObject(watcherObjectOptionalUUID(id), Optional.ofNullable(value)); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableOptionalIBlockData(final int id, + final @Nullable Object value) { + return new WrappedWatchableObject(watcherObjectOptionalIBlockData(id), Optional.ofNullable(value)); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableNBTTagCompound(final int id, final @NotNull Object value) { + return new WrappedWatchableObject(watcherObjectNBTTagCompound(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableParticle(final int id, final @NotNull Object value) { + return new WrappedWatchableObject(watcherObjectParticle(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableVillagerData(final int id, final @NotNull Object value) { + return new WrappedWatchableObject(watcherObjectVillagerData(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableOptional(final int id, final @Nullable Integer value) { + return new WrappedWatchableObject(watcherObjectOptionalInt(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableEntityPose(final int id, final @NotNull Object value) { + return new WrappedWatchableObject(watcherObjectEntityPose(id), value); + } + + // Unsupported types + + @Override + public @NotNull WrappedWatchableObject createWatchable(final int id, final short value) { + return new WrappedWatchableObject(watcherObjectShort(id), value); + } + + @Override + public @NotNull WrappedWatchableObject createWatchableObject(final int id, final Object value) { + return new WrappedWatchableObject(id, value); + } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + private final class StandardDataWatcherModifier implements DataWatcherModifier { + + private final @NotNull WrappedDataWatcher dataWatcher; + + @Override + public @NotNull WrappedDataWatcher dataWatcher() { + return dataWatcher; + } + + @Override + @SuppressWarnings("MethodDoesntCallSuperMethod") + public @NotNull DataWatcherModifier clone() { + return new StandardDataWatcherModifier(dataWatcher.deepClone()); + } + + // Actual types + + @Override + public @NotNull DataWatcherModifier set(final int id, final byte value) { + dataWatcher.setObject(watcherObjectByte(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier set(final int id, final int value) { + dataWatcher.setObject(watcherObjectInt(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier set(final int id, final float value) { + dataWatcher.setObject(watcherObjectFloat(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier set(final int id, final @NonNull String value) { + dataWatcher.setObject(watcherObjectString(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setIChatBaseComponent(final int id, final @NonNull Object value) { + dataWatcher.setObject(watcherObjectIChatBaseComponent(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setOptionalIChatBaseComponent(final int id, final @Nullable Object value) { + dataWatcher.setObject(watcherObjectOptionalIChatBaseComponent(id), Optional.ofNullable(value)); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setItemStack(int id, @NotNull Object value) { + dataWatcher.setObject(watcherObjectItemStack(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier set(final int id, final boolean value) { + dataWatcher.setObject(watcherObjectBoolean(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setVector3f(final int id, final @NonNull Object value) { + dataWatcher.setObject(watcherObjectVector3f(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setBlockPosition(final int id, final @NotNull Object value) { + dataWatcher.setObject(watcherObjectBlockPosition(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setOptionalBlockPosition(final int id, final @Nullable Object value) { + dataWatcher.setObject(watcherObjectOptionalBlockPosition(id), Optional.ofNullable(value)); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setEnumDirection(final int id, final @NonNull Object value) { + dataWatcher.setObject(watcherObjectEnumDirection(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setOptional(final int id, final @Nullable UUID value) { + dataWatcher.setObject(watcherObjectOptionalUUID(id), Optional.ofNullable(value)); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setOptionalIBlockData(final int id, final @Nullable Object value) { + dataWatcher.setObject(watcherObjectOptionalIBlockData(id), Optional.ofNullable(value)); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setNBTTagCompound(final int id, final @NotNull Object value) { + dataWatcher.setObject(watcherObjectNBTTagCompound(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setParticle(final int id, final @NotNull Object value) { + dataWatcher.setObject(watcherObjectParticle(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setVillagerData(final int id, final @NotNull Object value) { + dataWatcher.setObject(watcherObjectVillagerData(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setOptional(final int id, final @Nullable Integer value) { + dataWatcher.setObject(watcherObjectOptionalInt(id), Optional.ofNullable(value)); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setEntityPose(final int id, final @NotNull Object value) { + dataWatcher.setObject(watcherObjectEntityPose(id), value); + + return this; + } + // Unsupported types + + @Override + public @NotNull DataWatcherModifier set(final int id, final short value) { + dataWatcher.setObject(watcherObjectShort(id), value); + + return this; + } + + @Override + public @NotNull DataWatcherModifier setObject(final int id, final Object value) { + dataWatcher.setObject(id, value); + + return this; + } + } +} diff --git a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/protocol/misc/PacketListeners.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/protocol/misc/PacketListeners.java similarity index 66% rename from nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/protocol/misc/PacketListeners.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/protocol/misc/PacketListeners.java index f9a3b8c40..57c19741f 100644 --- a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/protocol/misc/PacketListeners.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/protocol/misc/PacketListeners.java @@ -1,10 +1,12 @@ -package ru.progrm_jarvis.minecraft.nmsutils.protocol.misc; +package ru.progrm_jarvis.minecraft.commons.nms.protocol.misc; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; -import lombok.*; +import lombok.Getter; +import lombok.NonNull; +import lombok.ToString; import lombok.experimental.UtilityClass; import org.bukkit.plugin.Plugin; @@ -15,24 +17,24 @@ @UtilityClass public class PacketListeners { - public PacketListener callbackPacketListener(@NonNull final Plugin plugin, - @NonNull final Consumer inboundPacketCallback, - @NonNull final Consumer outboundPacketCallback, - @NonNull final PacketType... packetTypes) { + public PacketListener callbackPacketListener(final @NonNull Plugin plugin, + final @NonNull Consumer inboundPacketCallback, + final @NonNull Consumer outboundPacketCallback, + final @NonNull PacketType... packetTypes) { return new CallbackServerPacketListener(plugin, inboundPacketCallback, outboundPacketCallback, packetTypes); } - public PacketListener callbackPacketListener(@NonNull final Plugin plugin, - @NonNull final Consumer inboundPacketCallback, - @NonNull final Consumer outboundPacketCallback, - @NonNull final Iterable packetTypes) { + public PacketListener callbackPacketListener(final @NonNull Plugin plugin, + final @NonNull Consumer inboundPacketCallback, + final @NonNull Consumer outboundPacketCallback, + final @NonNull Iterable packetTypes) { return new CallbackServerPacketListener(plugin, inboundPacketCallback, outboundPacketCallback, packetTypes); } - public PacketListener callbackPacketListener(@NonNull final Plugin plugin, - @NonNull final Consumer inboundPacketCallback, - @NonNull final Consumer outboundPacketCallback, - @NonNull final PacketCategory... packetCategories) { + public PacketListener callbackPacketListener(final @NonNull Plugin plugin, + final @NonNull Consumer inboundPacketCallback, + final @NonNull Consumer outboundPacketCallback, + final @NonNull PacketCategory... packetCategories) { return callbackPacketListener(plugin, inboundPacketCallback, outboundPacketCallback, Arrays .stream(packetCategories) .flatMap(packetCategory -> Arrays.stream(packetCategory.packetTypes)) @@ -40,10 +42,10 @@ public PacketListener callbackPacketListener(@NonNull final Plugin plugin, ); } - public PacketListener callbackPacketListener(@NonNull final Plugin plugin, - @NonNull final Consumer inboundPacketCallback, - @NonNull final Consumer outboundPacketCallback, - @NonNull final Collection packetCategories) { + public PacketListener callbackPacketListener(final @NonNull Plugin plugin, + final @NonNull Consumer inboundPacketCallback, + final @NonNull Consumer outboundPacketCallback, + final @NonNull Collection packetCategories) { return callbackPacketListener(plugin, inboundPacketCallback, outboundPacketCallback, packetCategories .stream() .flatMap(packetCategory -> Arrays.stream(packetCategory.packetTypes)) @@ -55,8 +57,8 @@ public PacketListener callbackPacketListener(@NonNull final Plugin plugin, @ToString public enum PacketCategory { PROTOCOL(PacketType.Protocol.class), - LEGACY_CLIENT(PacketType.Legacy.Client.class), - LEGACY_SERVER(PacketType.Legacy.Server.class), + //LEGACY_CLIENT(PacketType.Legacy.Client.class), + //LEGACY_SERVER(PacketType.Legacy.Server.class), LOGIN_CLIENT(PacketType.Login.Client.class), LOGIN_SERVER(PacketType.Login.Server.class), STATUS_CLIENT(PacketType.Status.Client.class), @@ -68,18 +70,18 @@ public enum PacketCategory { private final PacketType[] packetTypes; - PacketCategory(@NonNull final Class containingClass) { + PacketCategory(final @NonNull Class containingClass) { packetTypes = packetTypesFromContainingClass(containingClass); } } - private static PacketType[] packetTypesFromContainingClass(@NonNull final Class clazz) { + private static PacketType[] packetTypesFromContainingClass(final @NonNull Class clazz) { return Arrays.stream(clazz.getDeclaredFields()) .filter(field -> PacketType.class.isAssignableFrom(field.getType())) .map(field -> { try { return field.get(null); - } catch (IllegalAccessException e) { + } catch (final IllegalAccessException e) { throw new RuntimeException(e); } }) @@ -91,20 +93,20 @@ private static class CallbackServerPacketListener extends PacketAdapter { @NonNull private final Consumer inboundPacketCallback; @NonNull private final Consumer outboundPacketCallback; - public CallbackServerPacketListener(@NonNull final Plugin plugin, - @NonNull final Consumer inboundPacketCallback, - @NonNull final Consumer outboundPacketCallback, - @NonNull final PacketType... packetTypes) { + public CallbackServerPacketListener(final @NonNull Plugin plugin, + final @NonNull Consumer inboundPacketCallback, + final @NonNull Consumer outboundPacketCallback, + final @NonNull PacketType... packetTypes) { super(plugin, packetTypes); this.inboundPacketCallback = inboundPacketCallback; this.outboundPacketCallback = outboundPacketCallback; } - public CallbackServerPacketListener(@NonNull final Plugin plugin, - @NonNull final Consumer inboundPacketCallback, - @NonNull final Consumer outboundPacketCallback, - @NonNull final Iterable packetTypes) { + public CallbackServerPacketListener(final @NonNull Plugin plugin, + final @NonNull Consumer inboundPacketCallback, + final @NonNull Consumer outboundPacketCallback, + final @NonNull Iterable packetTypes) { super(plugin, packetTypes); this.inboundPacketCallback = inboundPacketCallback; diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/protocol/misc/PacketWrapperPacketAssociations.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/protocol/misc/PacketWrapperPacketAssociations.java new file mode 100644 index 000000000..5a473be2f --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/protocol/misc/PacketWrapperPacketAssociations.java @@ -0,0 +1,199 @@ +package ru.progrm_jarvis.minecraft.commons.nms.protocol.misc; + +import com.comphenix.packetwrapper.AbstractPacket; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketContainer; +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import lombok.*; +import lombok.experimental.FieldDefaults; +import lombok.experimental.UtilityClass; +import org.apache.commons.lang3.StringUtils; +import ru.progrm_jarvis.javacommons.collection.MapFiller; +import ru.progrm_jarvis.javacommons.object.Pair; +import ru.progrm_jarvis.minecraft.commons.util.SystemPropertyUtil; + +import java.lang.invoke.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Stream; + +import static java.lang.invoke.LambdaMetafactory.metafactory; +import static java.lang.invoke.MethodType.methodType; + +/** + * Utility for linking ProtocolLib's packer-related objects. + */ +@UtilityClass +public class PacketWrapperPacketAssociations { + + private final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + private final MethodType FUNCTION__METHOD_TYPE = methodType(Function.class); + private final MethodType VOID_PACKET_CONTAINER__METHOD_TYPE = methodType(void.class, PacketContainer.class); + + private final String FUNCTION__APPLY__METHOD_NAME = "apply"; + + /** + * Immutable bi-directional map of packet types and their IDs + */ + @NonNull public final BiMap PACKET_TYPES = ImmutableBiMap.copyOf( + MapFiller.from(new HashMap()) + .fill(fieldPacketTypes(PacketType.Handshake.Client.class, "Handshake", PacketDirection.CLIENT)) + .fill(fieldPacketTypes(PacketType.Handshake.Server.class, "Handshake", PacketDirection.SERVER)) + .fill(fieldPacketTypes(PacketType.Login.Client.class, "Login", PacketDirection.CLIENT)) + .fill(fieldPacketTypes(PacketType.Login.Server.class, "Login", PacketDirection.SERVER)) + .fill(fieldPacketTypes(PacketType.Play.Client.class, "Play", PacketDirection.CLIENT)) + .fill(fieldPacketTypes(PacketType.Play.Server.class, "Play", PacketDirection.SERVER)) + .fill(fieldPacketTypes(PacketType.Status.Client.class, "Status", PacketDirection.CLIENT)) + .fill(fieldPacketTypes(PacketType.Status.Server.class, "Status", PacketDirection.SERVER)) + .map() + ); + + private final Map> PACKET_CREATORS + = new ConcurrentHashMap<>(); + + private Stream> fieldPacketTypes(final @NonNull Class packetType, + final @NonNull String group, + final @NonNull PacketDirection direction) { + return Arrays.stream(packetType.getDeclaredFields()) + .filter(field -> PacketType.class.isAssignableFrom(field.getType())) + //.filter(field -> !field.isAnnotationPresent(Deprecated.class)) + .map(field -> { + final PacketType fieldValue; + try { + fieldValue = (PacketType) field.get(null); + } catch (final IllegalAccessException e) { + throw new IllegalStateException("Could not read value of field " + field); + } + return Pair.of( + fieldValue, + PacketTypeId.of(group, direction, upperCaseNameToUpperCamelCase(field.getName())) + ); + }); + } + + private String upperCaseNameToUpperCamelCase(final @NonNull String name) { + val split = StringUtils.split(name, '_'); + + val camelCase = new StringBuilder(); + for (val word : split) + if (word.length() != 0) camelCase + .append(StringUtils.capitalize(StringUtils.lowerCase(word))); + else camelCase.append("_"); + + return camelCase.toString(); + } + + /** + * Creates new packet wrapper of a valid type from the specified packet container object. + * + * @param packet packet to wrap using packet wrapper + * @return created packet wrapper object for the packet + */ + public AbstractPacket createPacketWrapper(final @NonNull PacketContainer packet) { + return PACKET_CREATORS + .computeIfAbsent(packet.getType(), packetType -> { + final Class packetWrapperClass; + { + val className = PACKET_TYPES.get(packetType).toPacketWrapperClassName(); + try { + packetWrapperClass = Class.forName(className); + } catch (final ClassNotFoundException e) { + throw new IllegalStateException("Could not find class by name \"" + className + '"'); + } + } + + + MethodHandle constructorMethodHandle; + try { + constructorMethodHandle = LOOKUP.findConstructor( + packetWrapperClass, VOID_PACKET_CONTAINER__METHOD_TYPE + ); + } catch (final NoSuchMethodException | IllegalAccessException e) { + throw new IllegalStateException( + "Cannot create method handle for constructor " + + packetWrapperClass + "(PacketContainer)", e + ); + } + + val type = constructorMethodHandle.type(); + + final CallSite callSite; + try { + callSite = metafactory( + LOOKUP, FUNCTION__APPLY__METHOD_NAME, FUNCTION__METHOD_TYPE, + VOID_PACKET_CONTAINER__METHOD_TYPE, constructorMethodHandle, type + ); + } catch (LambdaConversionException e) { + throw new IllegalStateException( + "Cannot invoke metafactory for constructor method-handle " + constructorMethodHandle, e + ); + } + + constructorMethodHandle = callSite.getTarget(); + + try { + //noinspection unchecked + return (Function) constructorMethodHandle.invokeExact(); + } catch (final Throwable x) { + throw new IllegalStateException( + "Cannot invoke metafactory-provided method-handle " + constructorMethodHandle, x + ); + } + }) + .apply(packet); + } + + /** + * Direction of the packet. + */ + @RequiredArgsConstructor + private enum PacketDirection { + + /** + * Packet going to client + */ + CLIENT("Client"), + + /** + * Packet going to the server + */ + SERVER("Server"); + + private final String name; + } + + @Value(staticConstructor = "of") + @FieldDefaults(level = AccessLevel.PROTECTED) + private static class PacketTypeId { + + private static final String PACKET_WRAPPER_PACKAGE = SystemPropertyUtil.getSystemProperty( + PacketTypeId.class.getCanonicalName() + "-packet-wrapper-package", + Function.identity(), "com.comphenix.packetwrapper" + ); + + /** + * Group of packets to which the one belongs + */ + @NonNull String group; + + /** + * Direction of the packet + */ + @NonNull PacketDirection direction; + + /** + * Name of the packet in the system + */ + @NonNull String name; + + @NonNull + private String toPacketWrapperClassName() { + return PACKET_WRAPPER_PACKAGE + ".Wrapper" + group + direction.name() + name; + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/protocol/misc/PacketWrapperUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/protocol/misc/PacketWrapperUtil.java new file mode 100644 index 000000000..77b641f8c --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/nms/protocol/misc/PacketWrapperUtil.java @@ -0,0 +1,100 @@ +package ru.progrm_jarvis.minecraft.commons.nms.protocol.misc; + +import com.comphenix.packetwrapper.AbstractPacket; +import com.google.common.base.Function; +import com.google.common.base.MoreObjects; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import lombok.NonNull; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import lombok.val; +import org.jetbrains.annotations.Nullable; +import ru.progrm_jarvis.reflector.wrapper.DynamicFieldWrapper; +import ru.progrm_jarvis.reflector.wrapper.DynamicMethodWrapper; +import ru.progrm_jarvis.reflector.wrapper.invoke.InvokeDynamicMethodWrapper; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +@UtilityClass +public class PacketWrapperUtil { + + private final String FIELDS_CACHE_CONCURRENCY_LEVEL_PROPERTY_NAME + = PacketWrapperUtil.class.getCanonicalName() + ".FIELDS_CACHE_CONCURRENCY_LEVEL", + METHODS_CACHE_CONCURRENCY_LEVEL_PROPERTY_NAME + = PacketWrapperUtil.class.getCanonicalName() + ".METHODS_CACHE_CONCURRENCY_LEVEL"; + + private final Cache, Map>> FIELDS_CACHE + = CacheBuilder.newBuilder() + .concurrencyLevel(Integer.parseInt(MoreObjects.firstNonNull( + System.getProperty(FIELDS_CACHE_CONCURRENCY_LEVEL_PROPERTY_NAME), "2") + )) + .build(); + + private final Cache, Map>> METHODS_CACHE + = CacheBuilder.newBuilder() + .concurrencyLevel(Integer.parseInt(MoreObjects.firstNonNull( + System.getProperty(METHODS_CACHE_CONCURRENCY_LEVEL_PROPERTY_NAME), "2") + )) + .build(); + + @SneakyThrows + public String toString(final @Nullable AbstractPacket packet) { + if (packet == null) return "null"; + + final Map> methods; + final String className; + { + val clazz = packet.getClass(); + methods = METHODS_CACHE.get(clazz, () -> Arrays.stream(clazz.getDeclaredMethods()) + .filter(method -> method.getName().startsWith("get")) + .collect(Collectors.toMap( + method -> getterNameToString(method.getName()), + (Function>) InvokeDynamicMethodWrapper::from + ))); + className = clazz.getName(); + } + + if (methods.isEmpty()) return className + "{}"; + + val stringBuilder = new StringBuilder(className) + .append('{'); + + { + val entries = methods.entrySet().iterator(); + boolean hasNext = entries.hasNext(); + while (hasNext) { + val entry = entries.next(); + // invoked before append() to exit if an exception occurs without any unneeded operations + val value = entry.getValue().invoke(packet); + + stringBuilder + .append(entry.getKey()) + .append(value); + + hasNext = entries.hasNext(); + if (hasNext) stringBuilder + .append(',') + .append(' '); + } + } + + return stringBuilder + .append('}') + .toString(); + } + + public String getterNameToString(final @NonNull String getterName) { + if (getterName.startsWith("get")) { + val name = getterName.substring(3); + if (name.length() == 0) return "get"; + + return name.substring(0, 1).toLowerCase() + name.substring(1); + } + return getterName; + } + +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/PlayerUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/PlayerUtil.java new file mode 100644 index 000000000..bbeff8a2a --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/PlayerUtil.java @@ -0,0 +1,61 @@ +package ru.progrm_jarvis.minecraft.commons.player; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import lombok.NonNull; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.progrm_jarvis.javacommons.object.ObjectUtil; +import ru.progrm_jarvis.minecraft.commons.util.SystemPropertyUtil; + +import java.util.Collection; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +@UtilityClass +public class PlayerUtil { + + private final String NAMES_CACHE_CONCURRENCY_LEVEL_PROPERTY_NAME + = PlayerUtil.class.getCanonicalName() + ".FIELDS_CACHE_CONCURRENCY_LEVEL"; + + @NonNull private final Cache<@NotNull UUID, @Nullable String> NAMES_CACHE = CacheBuilder.newBuilder() + .softValues() + .concurrencyLevel(SystemPropertyUtil.getSystemPropertyInt(NAMES_CACHE_CONCURRENCY_LEVEL_PROPERTY_NAME, 2)) + .build(); + + /** + * Gets online players as a collection of exact {@link Player} type. + * + * @return a view of currently online players cast to exact type + */ + @SuppressWarnings("unchecked") + public Collection getOnlinePlayers() { + return (Collection) Bukkit.getOnlinePlayers(); + } + + public Collection playersAround(final @NonNull Location location, final double radius) { + return Bukkit.getOnlinePlayers().stream() + .filter(player -> player.getLocation().distance(location) <= radius) + .collect(Collectors.toList()); + } + + public Collection playerEyesAround(final @NonNull Location location, final double radius) { + return Bukkit.getOnlinePlayers().stream() + .filter(player -> player.getEyeLocation().distance(location) <= radius) + .collect(Collectors.toList()); + } + + @SneakyThrows + public static Optional getPlayerName(final @NonNull UUID uuid) { + return Optional.ofNullable(NAMES_CACHE.get( + uuid, () -> ObjectUtil.mapOnlyNonNull(OfflinePlayer::getName, Bukkit.getOfflinePlayer(uuid)) + )); + } +} diff --git a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/collection/PlayerContainer.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/collection/PlayerContainer.java similarity index 69% rename from player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/collection/PlayerContainer.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/collection/PlayerContainer.java index a4895e1b5..2d226d548 100644 --- a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/collection/PlayerContainer.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/collection/PlayerContainer.java @@ -1,17 +1,26 @@ -package ru.progrm_jarvis.minecraft.playerutils.collection; +package ru.progrm_jarvis.minecraft.commons.player.collection; import lombok.val; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import ru.progrm_jarvis.minecraft.commons.player.PlayerUtil; import java.util.Collection; -import java.util.Collections; /** * An object which may store players. */ public interface PlayerContainer { + /** + * Gets whether or not this player container should be global. + * Global player container means that it considers all online players + * + * @return whether or not this fake entity is global + * + * @apiNote When global, all online players should be added on initialization of the object. + */ + boolean isGlobal(); + /** * Adds a player to this container. * @@ -39,7 +48,7 @@ default void addPlayers(final Collection players) { } default void addOnlinePlayers() { - addPlayers(Collections.unmodifiableCollection(Bukkit.getOnlinePlayers())); + addPlayers(PlayerUtil.getOnlinePlayers()); } /** @@ -54,7 +63,7 @@ default void addOnlinePlayers() { * * @param players players to remove */ - default void removePlayesr(final Player... players) { + default void removePlayers(final Player... players) { for (val player : players) removePlayer(player); } @@ -68,9 +77,10 @@ default void removePlayers(final Collection players) { } /** - * Returns {@code true} if this container contains the specified player and {@code false} otherwise. + * Checks if this container contains the specified player. * * @param player player to check for containment + * @return {@code true} if this container contains the specified player and {@code false} otherwise. */ boolean containsPlayer(Player player); @@ -79,5 +89,5 @@ default void removePlayers(final Collection players) { * * @return all players contained */ - Collection getPlayers(); + Collection getPlayers(); } diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/collection/PlayerContainers.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/collection/PlayerContainers.java new file mode 100644 index 000000000..f471fc1f4 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/collection/PlayerContainers.java @@ -0,0 +1,114 @@ +package ru.progrm_jarvis.minecraft.commons.player.collection; + +import lombok.NonNull; +import lombok.Value; +import lombok.experimental.UtilityClass; +import lombok.val; +import org.bukkit.entity.Player; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +@UtilityClass +public class PlayerContainers { + + public PlayerContainer wrap(final @NonNull Collection collectionOfPlayers, + final boolean global) { + return new PlayerContainerCollectionWrapper(collectionOfPlayers, global); + } + + public PlayerContainer wrap(final @NonNull Map mapOfPlayers, + final @NonNull Function defaultValueSupplier, + final boolean global) { + if (global) { + val container = new PlayerContainerMapWrapper<>(mapOfPlayers, defaultValueSupplier, true); + container.addOnlinePlayers(); + + return container; + } + + return new PlayerContainerMapWrapper<>(mapOfPlayers, defaultValueSupplier, false); + } + + // non-global + public PlayerContainer wrap(final @NonNull Map mapOfPlayers) { + return new PlayerContainerMapWrapper<>(mapOfPlayers, player -> { + throw new UnsupportedOperationException("Players cannot be directly added to this non-global player-map"); + }, false); + } + + @Value + private static class PlayerContainerCollectionWrapper implements PlayerContainer { + + @NonNull Collection collection; + boolean global; + + public PlayerContainerCollectionWrapper(final @NonNull Collection collection, final boolean global) { + this.collection = collection; + this.global = global; + + if (global) addOnlinePlayers(); + } + + @Override + public void addPlayer(final Player player) { + collection.add(player); + } + + @Override + public void removePlayer(final Player player) { + collection.remove(player); + } + + @Override + public boolean containsPlayer(final Player player) { + return collection.contains(player); + } + + @Override + public Collection getPlayers() { + return collection; + } + } + + @Value + private static class PlayerContainerMapWrapper implements PlayerContainer { + + @NonNull Map map; + @NonNull Set playersView; + @NonNull Function defaultValueSupplier; + boolean global; + + public PlayerContainerMapWrapper(final @NonNull Map map, + final @NonNull Function defaultValueSupplier, + final boolean global) { + this.map = map; + playersView = Collections.unmodifiableSet(map.keySet()); + this.defaultValueSupplier = defaultValueSupplier; + this.global = global; + } + + @Override + public void addPlayer(final Player player) { + map.put(player, defaultValueSupplier.apply(player)); + } + + @Override + public void removePlayer(final Player player) { + map.remove(player); + } + + @Override + public boolean containsPlayer(final Player player) { + return map.containsKey(player); + } + + @Override + public Collection getPlayers() { + return playersView; + } + } +} diff --git a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/package-info.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/package-info.java similarity index 63% rename from player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/package-info.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/package-info.java index 4a9f15ba7..11b3ea7ed 100644 --- a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/package-info.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/package-info.java @@ -1,4 +1,4 @@ /** * A set of utilities useful for {@link org.bukkit.entity.Player} interactions. */ -package ru.progrm_jarvis.minecraft.playerutils; \ No newline at end of file +package ru.progrm_jarvis.minecraft.commons.player; \ No newline at end of file diff --git a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/registry/DefaultPlayerRegistry.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/registry/DefaultPlayerRegistry.java similarity index 60% rename from player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/registry/DefaultPlayerRegistry.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/registry/DefaultPlayerRegistry.java index 9ac730347..2c3217db1 100644 --- a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/registry/DefaultPlayerRegistry.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/registry/DefaultPlayerRegistry.java @@ -1,20 +1,17 @@ -package ru.progrm_jarvis.minecraft.playerutils.registry; +package ru.progrm_jarvis.minecraft.commons.player.registry; import com.google.common.base.Preconditions; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NonNull; +import lombok.*; import lombok.experimental.FieldDefaults; -import lombok.val; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.plugin.Plugin; -import ru.progrm_jarvis.minecraft.playerutils.collection.PlayerContainer; +import ru.progrm_jarvis.minecraft.commons.player.collection.PlayerContainer; import java.util.Collections; import java.util.HashSet; @@ -25,29 +22,45 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +@ToString +@EqualsAndHashCode @FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) public class DefaultPlayerRegistry implements PlayerRegistry { @NonNull Plugin plugin; - @NonNull @Getter Set players; + @Getter @NonNull Set players; + @Getter boolean global; @NonNull Set playerContainers = Collections.newSetFromMap(new WeakHashMap<>()); - private ReadWriteLock playerContainersLock = new ReentrantReadWriteLock(); - Lock playerContainersReadLock = playerContainersLock.readLock(); - Lock playerContainersWriteLock = playerContainersLock.writeLock(); + Lock playerContainersReadLock; + Lock playerContainersWriteLock; - public DefaultPlayerRegistry(@NonNull final Plugin plugin, @NonNull final Set playerSet, - final long checkInterval) { + Listener listener; + + public DefaultPlayerRegistry(final @NonNull Plugin plugin, final @NonNull Set playerSet, + final boolean global) { Preconditions.checkArgument(playerSet.isEmpty(), "playerSet should be empty"); this.plugin = plugin; players = playerSet; + this.global = global; - Bukkit.getPluginManager().registerEvents(new Listener() { + { + ReadWriteLock playerContainersLock = new ReentrantReadWriteLock(); + playerContainersReadLock = playerContainersLock.readLock(); + playerContainersWriteLock = playerContainersLock.writeLock(); + } + plugin.getServer().getPluginManager().registerEvents(listener = global ? new Listener() { @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public final void onPlayerJoin(final PlayerJoinEvent event) { - addPlayer(event.getPlayer()); + addPlayer(event.getPlayer(), false); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + public final void onPlayerQuit(final PlayerQuitEvent event) { + removePlayer(event.getPlayer()); } + } : new Listener() { @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public final void onPlayerQuit(final PlayerQuitEvent event) { @@ -56,33 +69,38 @@ public final void onPlayerQuit(final PlayerQuitEvent event) { }, plugin); } - public DefaultPlayerRegistry(@NonNull final Plugin plugin, final boolean concurrent, final long checkInterval) { - this(plugin, concurrent ? new HashSet<>() : ConcurrentHashMap.newKeySet(), checkInterval); + public DefaultPlayerRegistry(final @NonNull Plugin plugin, final boolean concurrent, final boolean global) { + this(plugin, concurrent ? new HashSet<>() : ConcurrentHashMap.newKeySet(), global); } - public DefaultPlayerRegistry(@NonNull final Plugin plugin, final long checkInterval) { - this(plugin, true, checkInterval); + public DefaultPlayerRegistry(final @NonNull Plugin plugin) { + this(plugin, true, true); } - @Override - public void addPlayer(final Player player) { + public void addPlayer(final Player player, final boolean force) { players.add(player); - Bukkit.getScheduler().runTask(plugin, () -> { + plugin.getServer().getScheduler().runTask(plugin, () -> { playerContainersReadLock.lock(); try { - for (val playerContainer : playerContainers) playerContainer.addPlayer(player); + for (val playerContainer : playerContainers) if (force || playerContainer.isGlobal()) playerContainer + .addPlayer(player); } finally { playerContainersReadLock.unlock(); } }); } + @Override + public void addPlayer(final Player player) { + addPlayer(player, true); + } + @Override public void removePlayer(final Player player) { players.remove(player); - Bukkit.getScheduler().runTask(plugin, () -> { + plugin.getServer().getScheduler().runTask(plugin, () -> { playerContainersReadLock.lock(); try { for (val playerContainer : playerContainers) playerContainer.removePlayer(player); @@ -120,4 +138,9 @@ public C unregister(final C playerContainer) { return playerContainer; } + + @Override + public void shutdown() { + HandlerList.unregisterAll(listener); + } } diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/registry/PlayerRegistries.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/registry/PlayerRegistries.java new file mode 100644 index 000000000..2107859cb --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/registry/PlayerRegistries.java @@ -0,0 +1,46 @@ +package ru.progrm_jarvis.minecraft.commons.player.registry; + +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import ru.progrm_jarvis.minecraft.commons.player.collection.PlayerContainer; +import ru.progrm_jarvis.minecraft.commons.player.collection.PlayerContainers; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +@UtilityClass +public class PlayerRegistries { + + private final Map DEFAULT_REGISTRIES = new ConcurrentHashMap<>(); + + public PlayerRegistry defaultRegistry(final @NonNull Plugin plugin) { + return DEFAULT_REGISTRIES.computeIfAbsent(plugin, DefaultPlayerRegistry::new); + } + + public C registerInDefaultRegistry(final @NonNull Plugin plugin, + final @NonNull C playerContainer) { + return defaultRegistry(plugin).register(playerContainer); + } + + public PlayerContainer registerInDefaultRegistry(final @NonNull Plugin plugin, + final @NonNull Collection collectionOfPlayers, + final boolean global) { + return registerInDefaultRegistry(plugin, PlayerContainers.wrap(collectionOfPlayers, global)); + } + + public PlayerContainer registerInDefaultRegistry(final @NonNull Plugin plugin, + final @NonNull Map mapOfPlayers, + final @NonNull Function defaultValueSupplier, + final boolean global) { + return registerInDefaultRegistry(plugin, PlayerContainers.wrap(mapOfPlayers, defaultValueSupplier, global)); + } + + public PlayerContainer registerInDefaultRegistry(final @NonNull Plugin plugin, + final @NonNull Map mapOfPlayers) { + return registerInDefaultRegistry(plugin, PlayerContainers.wrap(mapOfPlayers)); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/registry/PlayerRegistry.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/registry/PlayerRegistry.java new file mode 100644 index 000000000..928b77a0f --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/registry/PlayerRegistry.java @@ -0,0 +1,35 @@ +package ru.progrm_jarvis.minecraft.commons.player.registry; + +import lombok.NonNull; +import lombok.val; +import org.bukkit.entity.Player; +import ru.progrm_jarvis.minecraft.commons.player.collection.PlayerContainer; +import ru.progrm_jarvis.minecraft.commons.player.collection.PlayerContainers; +import ru.progrm_jarvis.minecraft.commons.util.shutdown.Shutdownable; + +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; + +public interface PlayerRegistry extends PlayerContainer, Shutdownable { + + C register(C playerContainer); + + C unregister(C playerContainer); + + default PlayerContainer register(final @NonNull Collection playerCollection, final boolean global) { + val playerContainer = PlayerContainers.wrap(playerCollection, global); + register(playerContainer); + + return playerContainer; + } + + default PlayerContainer register(final @NonNull Map playerCollection, + final @NonNull Function defaultValueSupplier, + final boolean global) { + val playerContainer = PlayerContainers.wrap(playerCollection, defaultValueSupplier, global); + register(playerContainer); + + return playerContainer; + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/registry/PlayerRegistryRegistration.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/registry/PlayerRegistryRegistration.java new file mode 100644 index 000000000..9e5ccd195 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/player/registry/PlayerRegistryRegistration.java @@ -0,0 +1,56 @@ +package ru.progrm_jarvis.minecraft.commons.player.registry; + +import java.lang.annotation.*; + +import static java.lang.annotation.ElementType.*; + +/** + * Marker to indicate how the object handles registration in {@link PlayerRegistry}. + * + * @apiNote When applied to constructor it indicates the policy for this exact constructor. When applied + * to the class itself it indicates the default policy for all constructors except for explicitly annotated. + */ +@Inherited +@Documented +@Target({TYPE, CONSTRUCTOR}) +@Retention(RetentionPolicy.CLASS) +public @interface PlayerRegistryRegistration { + + /** + * Type of object registration policy used by it. + * + * @return registration policy for the object + * + * @apiNote default value is not provided to force explicit specification + */ + Policy value(); + + /** + * The way registration in {@link PlayerRegistry} happens for this object. + */ + enum Policy { + + /** + * Object registers itself automatically and so is not expected to be registered manually. + * + * @apiNote if the object accepts {@link PlayerRegistry} in constructor which will be used for registration + * then it is also considered as an automatic registration. + */ + AUTO, + + /** + * The user of the object should register the object manually. + */ + MANUAL, + + /** + * The object doesn't register itself and the user of the object is not forces to. + */ + OPTIONAL, + + /** + * The object should not be registered. + */ + NEVER + } +} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BukkitPluginContainer.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BukkitPluginContainer.java similarity index 66% rename from commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BukkitPluginContainer.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BukkitPluginContainer.java index 6e8f9677c..c4c16913c 100644 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BukkitPluginContainer.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BukkitPluginContainer.java @@ -4,15 +4,13 @@ /** * An object which contains a bukkit plugin instance. - * - * @param

plugin on which this object depends */ -public interface BukkitPluginContainer

{ +public interface BukkitPluginContainer { /** * Gets the bukkit plugin contained by this object. * * @return bukkit plugin of this object */ - P getBukkitPlugin(); + Plugin getBukkitPlugin(); } diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BukkitPluginShutdownUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BukkitPluginShutdownUtil.java new file mode 100644 index 000000000..7f954ad00 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BukkitPluginShutdownUtil.java @@ -0,0 +1,83 @@ +package ru.progrm_jarvis.minecraft.commons.plugin; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.Value; +import lombok.experimental.FieldDefaults; +import lombok.experimental.UtilityClass; +import lombok.val; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.plugin.Plugin; +import ru.progrm_jarvis.javacommons.collection.concurrent.ConcurrentCollections; +import ru.progrm_jarvis.minecraft.commons.util.shutdown.Shutdownable; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Utility responsible for handling plugin shutdown hooks. + */ +@UtilityClass +public class BukkitPluginShutdownUtil { + + private final Map PLUGIN_SHUTDOWN_HANDLERS = new ConcurrentHashMap<>(); + + private PluginShutdownHandler getOrCreateShutdownHandler(final Plugin plugin) { + return PLUGIN_SHUTDOWN_HANDLERS.computeIfAbsent(plugin, PluginShutdownHandler::new); + } + + private Optional getOptionalShutdownHandler(final Plugin plugin) { + return Optional.ofNullable(PLUGIN_SHUTDOWN_HANDLERS.get(plugin)); + } + + public void addShutdownHook(final Plugin plugin, final Shutdownable callback) { + getOrCreateShutdownHandler(plugin).shutdownHooks.add(callback); + } + + public void addShutdownHooks(final Plugin plugin, final Shutdownable... callbacks) { + getOrCreateShutdownHandler(plugin).shutdownHooks.addAll(Arrays.asList(callbacks)); + } + + public void addShutdownHooks(final Plugin plugin, final Collection callbacks) { + getOrCreateShutdownHandler(plugin).shutdownHooks.addAll(callbacks); + } + + public void removeShutdownHook(final Plugin plugin, final Shutdownable callback) { + getOptionalShutdownHandler(plugin).ifPresent(handler -> handler.shutdownHooks.remove(callback)); + } + + public void removeShutdownHooks(final Plugin plugin, final Shutdownable... callbacks) { + getOptionalShutdownHandler(plugin).ifPresent(handler -> handler.shutdownHooks.removeAll(Arrays.asList(callbacks))); + } + + public void removeShutdownHooks(final Plugin plugin, final Collection callback) { + getOptionalShutdownHandler(plugin).ifPresent(handler -> handler.shutdownHooks.removeAll(callback)); + } + + @Value + @FieldDefaults(level = AccessLevel.PRIVATE) + private static class PluginShutdownHandler implements Listener { + + @NonNull Plugin plugin; + @NonNull List shutdownHooks = ConcurrentCollections.concurrentList(new ArrayList<>()); + + private PluginShutdownHandler(final @NonNull Plugin plugin) { + this.plugin = plugin; + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @EventHandler(ignoreCancelled = true) + public void onPluginDisable(final PluginDisableEvent event) { + // check if the plugin disabled is the right one + if (event.getPlugin() == plugin) { + val thisShutdownHooks = shutdownHooks; + while (!thisShutdownHooks.isEmpty()) { + val hook = thisShutdownHooks.remove(0); + hook.shutdown(); + } + } + } + } +} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BungeePluginContainer.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BungeePluginContainer.java similarity index 68% rename from commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BungeePluginContainer.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BungeePluginContainer.java index d11eba74c..4498c179e 100644 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BungeePluginContainer.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/plugin/BungeePluginContainer.java @@ -4,15 +4,13 @@ /** * An object which contains a bungeecord plugin instance. - * - * @param

plugin on which this object depends */ -public interface BungeePluginContainer

{ +public interface BungeePluginContainer { /** * Gets the bungeecord plugin contained by this object. * * @return bungeecord plugin of this object */ - P getBungeePlugin(); + Plugin getBungeePlugin(); } diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/chain/AbstractSchedulerChain.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/chain/AbstractSchedulerChain.java new file mode 100644 index 000000000..2463b096a --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/chain/AbstractSchedulerChain.java @@ -0,0 +1,127 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.chain; + +import lombok.*; +import lombok.experimental.Delegate; + +import javax.annotation.Nonnegative; +import java.util.Queue; + +public abstract class AbstractSchedulerChain implements SchedulerChain { + + @ToString + @RequiredArgsConstructor + protected abstract static class Builder implements SchedulerChain.Builder { + + final @NonNull Queue tasks; + + @Override + public SchedulerChain.Builder delay(final long delay) { + tasks.add(new PauseTask(delay)); + + return this; + } + + @Override + public SchedulerChain.Builder then(final @NonNull Runnable task) { + tasks.add(new UndelayedTask(task)); + + return this; + } + + @Override + public SchedulerChain.Builder then(final @NonNull Runnable task, final long delay) { + tasks.add(new DelayedTask(task, delay)); + + return this; + } + + @Override + public SchedulerChain.Builder thenRepeat(final @NonNull Runnable task, final long times) { + tasks.add(new UndelayedRecalledTask(task, times)); + + return this; + } + + @Override + public SchedulerChain.Builder thenRepeat(final @NonNull Runnable task, final long times, final long delay) { + tasks.add(new DelayedRecalledTask(task, delay, times)); + + return this; + } + } + + /** + * An entry of the chain. + */ + protected interface ChainedTask extends Runnable { + + /** + * Gets the delay after which this element entry should be called. + * + * @return delay of this chain entry + */ + long getDelay(); + + /** + * Gets the amount of times this task should be called. + * + * @return the amount of times this task should be called + * + * @apiNote the delay happens once after which the task is called multiple times without it + */ + @Nonnegative default long getRunTimes() { + return 1; + } + } + + @Value + protected static class PauseTask implements ChainedTask { + + long delay; + + @Override + public void run() {} // will even never be called + + @Override + public long getRunTimes() { + return 0; + } + } + + @Value + protected static class UndelayedTask implements ChainedTask { + + @Delegate @NonNull Runnable runnable; + + @Override + public long getDelay() { + return 0; + } + } + + @Value + protected static class UndelayedRecalledTask implements ChainedTask { + + @Delegate @NonNull Runnable runnable; + long runTimes; + + @Override + public long getDelay() { + return 0; + } + } + + @Value + protected static class DelayedTask implements ChainedTask { + + @Delegate @NonNull Runnable runnable; + long delay; + } + + @Value + protected static class DelayedRecalledTask implements ChainedTask { + + @Delegate @NonNull Runnable runnable; + long delay, runTimes; + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/chain/BukkitSchedulerChain.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/chain/BukkitSchedulerChain.java new file mode 100644 index 000000000..ec5611015 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/chain/BukkitSchedulerChain.java @@ -0,0 +1,118 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.chain; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import lombok.experimental.NonFinal; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicBoolean; + +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +public class BukkitSchedulerChain extends AbstractSchedulerChain { + + @NonNull AtomicBoolean running = new AtomicBoolean(); + + Plugin plugin; + @NonNull BukkitScheduler scheduler; + boolean async; + @NonNull Iterable tasks; + + final @NonNull Object currentTaskMutex; + + @NonFinal @NonNull volatile Iterator iterator; + @NonFinal volatile BukkitTask currentTask; + + public static SchedulerChain.Builder builder(final Plugin plugin, final boolean async) { + return new Builder(plugin, async, new ArrayDeque<>()); + } + + protected BukkitSchedulerChain(final @NonNull Plugin plugin, final boolean async, + final @NonNull Iterable tasks) { + this.plugin = plugin; + scheduler = plugin.getServer().getScheduler(); + this.async = async; + this.tasks = tasks; + + //noinspection ZeroLengthArrayAllocation: mutex object + currentTaskMutex = new Object[0]; + } + + @Override + public void run() { + if (running.compareAndSet(false, true)) { + iterator = tasks.iterator(); + + tryRunNextTask(); + } else throw new IllegalStateException("This BukkitSchedulerChain has already been started"); + } + + @Override + public void interrupt() { + if (running.get()) reset(); + } + + protected void reset() { + if (currentTask != null) { + synchronized (currentTaskMutex) { + if (currentTask != null) { + currentTask.cancel(); + currentTask = null; + } + } + } + running.set(false); + } + + protected void tryRunNextTask() { + if (iterator.hasNext()) { + val task = iterator.next(); + + val nextRunnable = createNextRunnable(task, task.getRunTimes()); + val delay = task.getDelay(); + + synchronized (currentTaskMutex) { + currentTask = async + ? scheduler.runTaskLaterAsynchronously(plugin, nextRunnable, delay) + : scheduler.runTaskLater(plugin, nextRunnable, delay); + } + } else reset(); + } + + protected Runnable createNextRunnable(final @NonNull Runnable runnable, final long times) { + return () -> { + var mutableTimes = times; + // call the required tasks needed amount of time + System.out.println("Run times: " + times); + while (mutableTimes-- > 0) runnable.run(); + + // pick next task (if there is one) and execute it + tryRunNextTask(); + }; + } + + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + protected static class Builder extends AbstractSchedulerChain.Builder { + + @NonNull Plugin plugin; + boolean async; + + public Builder(final @NonNull Plugin plugin, final boolean async, final @NonNull Queue tasks) { + super(tasks); + this.plugin = plugin; + this.async = async; + } + + @Override + public SchedulerChain build() { + return tasks.isEmpty() + ? EmptyStub.INSTANCE + : new BukkitSchedulerChain(plugin, async, new ArrayList<>(tasks)); + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/chain/SchedulerChain.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/chain/SchedulerChain.java new file mode 100644 index 000000000..d77a398c4 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/chain/SchedulerChain.java @@ -0,0 +1,37 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.chain; + +import lombok.NonNull; + +/** + * A chain of tasks called in the specified order. + */ +public interface SchedulerChain extends Runnable { + + void interrupt(); + + interface Builder { + + SchedulerChain build(); + + Builder delay(final long delay); + + Builder then(@NonNull Runnable task); + + Builder then(@NonNull Runnable task, final long delay); + + Builder thenRepeat(@NonNull Runnable task, final long times); + + Builder thenRepeat(@NonNull Runnable task, final long times, final long delay); + } + + final class EmptyStub implements SchedulerChain { + + public static final EmptyStub INSTANCE = new EmptyStub(); + + @Override + public void interrupt() {} + + @Override + public void run() {} + } +} diff --git a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/misc/KeyedSchedulerGroup.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/misc/KeyedSchedulerGroup.java similarity index 74% rename from scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/misc/KeyedSchedulerGroup.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/misc/KeyedSchedulerGroup.java index 2178c913e..83e6c7de7 100644 --- a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/misc/KeyedSchedulerGroup.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/misc/KeyedSchedulerGroup.java @@ -1,4 +1,4 @@ -package ru.progrm_jarvis.minecraft.schedulerutils.misc; +package ru.progrm_jarvis.minecraft.commons.schedule.misc; import lombok.NonNull; @@ -9,7 +9,7 @@ public abstract class KeyedSchedulerGroup extends Schedul public abstract void addTask(K key, @NonNull T task); @Override - public void addTask(@NonNull final T task) { + public void addTask(final @NonNull T task) { addTask(null, task); } diff --git a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/misc/SchedulerGroup.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/misc/SchedulerGroup.java similarity index 76% rename from scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/misc/SchedulerGroup.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/misc/SchedulerGroup.java index 2bf9590f1..8573a2724 100644 --- a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/misc/SchedulerGroup.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/misc/SchedulerGroup.java @@ -1,14 +1,14 @@ -package ru.progrm_jarvis.minecraft.schedulerutils.misc; +package ru.progrm_jarvis.minecraft.commons.schedule.misc; import lombok.NonNull; import lombok.val; -import org.bukkit.scheduler.BukkitRunnable; +import ru.progrm_jarvis.minecraft.commons.schedule.task.AbstractSchedulerRunnable; import java.util.ArrayList; import java.util.Collection; import java.util.function.Predicate; -public abstract class SchedulerGroup extends BukkitRunnable { +public abstract class SchedulerGroup extends AbstractSchedulerRunnable { public abstract int size(); @@ -23,14 +23,14 @@ public abstract class SchedulerGroup extends BukkitRunnable public abstract Collection clearTasks(); - public T findTask(@NonNull final Predicate predicate) { + public T findTask(final @NonNull Predicate predicate) { for (final T task : tasks()) if (predicate.test(task)) return task; return null; } - public T removeTask(@NonNull final Predicate predicate) { + public T removeTask(final @NonNull Predicate predicate) { val tasks = tasks().iterator(); while (tasks.hasNext()) { @@ -46,7 +46,7 @@ public T removeTask(@NonNull final Predicate predicate) { return null; } - public Collection removeTasks(@NonNull final Predicate predicate) { + public Collection removeTasks(final @NonNull Predicate predicate) { val tasks = tasks(); val removedTasks = new ArrayList(); for (val task : tasks()) if (predicate.test(task)) removedTasks.add(task); diff --git a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/misc/SchedulerGroups.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/misc/SchedulerGroups.java similarity index 84% rename from scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/misc/SchedulerGroups.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/misc/SchedulerGroups.java index f78300d16..c46b2c79b 100644 --- a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/misc/SchedulerGroups.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/misc/SchedulerGroups.java @@ -1,4 +1,4 @@ -package ru.progrm_jarvis.minecraft.schedulerutils.misc; +package ru.progrm_jarvis.minecraft.commons.schedule.misc; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; @@ -6,13 +6,12 @@ import lombok.*; import lombok.experimental.FieldDefaults; import lombok.experimental.UtilityClass; -import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.plugin.Plugin; -import ru.progrm_jarvis.minecraft.schedulerutils.task.initializer.BukkitTaskInitializer; -import ru.progrm_jarvis.minecraft.schedulerutils.task.initializer.BukkitTaskInitializers; +import ru.progrm_jarvis.minecraft.commons.schedule.task.initializer.BukkitTaskInitializer; +import ru.progrm_jarvis.minecraft.commons.schedule.task.initializer.BukkitTaskInitializers; import java.util.ArrayList; import java.util.Collection; @@ -23,13 +22,13 @@ @UtilityClass public class SchedulerGroups { - public KeyedSchedulerGroup keyedSchedulerGroup(@NonNull final Plugin plugin, + public KeyedSchedulerGroup keyedSchedulerGroup(final @NonNull Plugin plugin, final boolean async, final long delay, final long interval) { return new MultimapBasedKeyedSchedulerGroup<>(plugin, async, delay, interval, ArrayListMultimap.create()); } - public KeyedSchedulerGroup concurrentKeyedSchedulerGroup(@NonNull final Plugin plugin, + public KeyedSchedulerGroup concurrentKeyedSchedulerGroup(final @NonNull Plugin plugin, final boolean async, final long delay, final long interval) { @@ -44,21 +43,21 @@ public KeyedSchedulerGroup concurrentKeyedSchedule @FieldDefaults(level = AccessLevel.PROTECTED) private static class MultimapBasedKeyedSchedulerGroup extends KeyedSchedulerGroup { - @NonNull final Plugin plugin; + final @NonNull Plugin plugin; final BukkitTaskInitializer initializer; final Multimap tasks; - public MultimapBasedKeyedSchedulerGroup(@NonNull final Plugin plugin, final boolean async, final long delay, - final long interval, @NonNull final Multimap tasks) { + public MultimapBasedKeyedSchedulerGroup(final @NonNull Plugin plugin, final boolean async, final long delay, + final long interval, final @NonNull Multimap tasks) { this.plugin = plugin; initializer = BukkitTaskInitializers.createTimerTaskInitializer(plugin, async, delay, interval, this); this.tasks = tasks; // set up plugin disable hook - Bukkit.getPluginManager().registerEvents(new Listener() { + plugin.getServer().getPluginManager().registerEvents(new Listener() { @EventHandler public void onPluginDisable(final PluginDisableEvent event) { @@ -98,19 +97,19 @@ public void run() { } @Override - public void addTask(final K key, @NonNull final T task) { + public void addTask(final K key, final @NonNull T task) { initializer.initialize(); tasks.put(key, task); } @Override - public boolean removeTask(@NonNull final T task) { + public boolean removeTask(final @NonNull T task) { return tasks.values().remove(task); } @Override - public int removeTasks(@NonNull final T task) { + public int removeTasks(final @NonNull T task) { val tasks = this.tasks.values(); boolean mayContain; var removed = 0; @@ -137,9 +136,9 @@ private static class ConcurrentMultimapBasedKeyedSchedulerGroup tasks) { + final @NonNull Multimap tasks) { super(plugin, async, delay, interval, tasks); } @@ -184,7 +183,7 @@ public void run() { } @Override - public void addTask(final K key, @NonNull final T task) { + public void addTask(final K key, final @NonNull T task) { writeLock.lock(); try { super.addTask(key, task); @@ -194,7 +193,7 @@ public void addTask(final K key, @NonNull final T task) { } @Override - public boolean removeTask(@NonNull final T task) { + public boolean removeTask(final @NonNull T task) { writeLock.lock(); try { return super.removeTask(task); @@ -204,7 +203,7 @@ public boolean removeTask(@NonNull final T task) { } @Override - public int removeTasks(@NonNull final T task) { + public int removeTasks(final @NonNull T task) { writeLock.lock(); try { return super.removeTasks(task); diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/pool/LoopPool.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/pool/LoopPool.java new file mode 100644 index 000000000..25aad8c9e --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/pool/LoopPool.java @@ -0,0 +1,16 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.pool; + +import org.jetbrains.annotations.NotNull; + +@FunctionalInterface +public interface LoopPool { + + @NotNull ShutdownHook addTask(@NotNull Runnable task, long period, boolean async); + + @FunctionalInterface + interface ShutdownHook extends AutoCloseable { + + @Override + void close(); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/pool/SingleWorkerLoopPool.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/pool/SingleWorkerLoopPool.java new file mode 100644 index 000000000..a99ae7154 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/pool/SingleWorkerLoopPool.java @@ -0,0 +1,107 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.pool; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.experimental.NonFinal; +import lombok.val; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; + +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +public final class SingleWorkerLoopPool implements LoopPool { + + @NonNull Plugin plugin; + @NonNull BukkitScheduler scheduler; + @NonNull Long2ObjectMap syncTasks, asyncTasks; + + @Override + public @NotNull ShutdownHook addTask(final @NotNull Runnable task, final long period, final boolean async) { + return (async ? asyncTasks.computeIfAbsent(period, p -> { + val runner = new PaperTaskRunner(ConcurrentHashMap.newKeySet()); + runner.setup( + scheduler.runTaskTimer(plugin, runner, period, period), () -> asyncTasks.remove(period) + ); + + return runner; + }) : syncTasks.computeIfAbsent(period, p -> { + val runner = new PaperTaskRunner(ConcurrentHashMap.newKeySet()); + runner.setup( + scheduler.runTaskTimer(plugin, runner, period, period), () -> syncTasks.remove(period) + ); + return runner; + })).addTask(task); + } + + public static LoopPool create(final @NonNull Plugin plugin) { + return new SingleWorkerLoopPool( + plugin, plugin.getServer().getScheduler(), + new Long2ObjectOpenHashMap<>(4), new Long2ObjectOpenHashMap<>(4) + ); + } + + interface TaskRunner extends AutoCloseable { + + @NotNull ShutdownHook addTask(@NotNull Runnable task); + + @Override + void close(); + } + + interface BukkitTaskRunner extends TaskRunner, Runnable { + + void setup(@NotNull BukkitTask task, @NotNull Runnable disabler); + } + + @RequiredArgsConstructor + @FieldDefaults(level = AccessLevel.PUBLIC, makeFinal = true) + private static class PaperTaskRunner implements BukkitTaskRunner { + + @NotNull Collection tasks; + @NonFinal /* because class initialization order */ @Nullable BukkitTask owningTask; + @NonFinal /* because class initialization order */ @Nullable Runnable disabler; + + public void run() { + for (val task : tasks) task.run(); + } + + @Override + public void setup(final @NotNull BukkitTask task, final @NotNull Runnable disabler) { + owningTask = task; + this.disabler = disabler; + } + + @Override + public @NotNull ShutdownHook addTask(final @NotNull Runnable task) { + tasks.add(task); + + return () -> removeTask(task); + } + + private void removeTask(final @NotNull Runnable task) { + tasks.remove(task); + if (tasks.isEmpty() && owningTask != null) { + assert disabler != null; + disabler.run(); + + close(); + } + } + + @Override + public void close() { + assert owningTask != null; + owningTask.cancel(); + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/AbstractSchedulerRunnable.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/AbstractSchedulerRunnable.java new file mode 100644 index 000000000..728bbdab3 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/AbstractSchedulerRunnable.java @@ -0,0 +1,84 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.task; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.Synchronized; +import lombok.experimental.FieldDefaults; +import lombok.experimental.NonFinal; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.NotNull; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +public abstract class AbstractSchedulerRunnable implements SchedulerRunnable { + + @NonFinal volatile BukkitTask task = null; + @SuppressWarnings("ZeroLengthArrayAllocation") Object taskAccessLock = new Object[0]; + + @Override + @Synchronized("taskAccessLock") + public boolean isCancelled() { + final BukkitTask task; + if ((task = this.task) == null) throw new IllegalStateException("This task has not yet started"); + + return task.isCancelled(); + } + + @Override + @Synchronized("taskAccessLock") + public void cancel() { + final BukkitTask task; + if ((task = this.task) == null) throw new IllegalStateException("This task has not yet started"); + + task.cancel(); + } + + @Override + @Synchronized("taskAccessLock") + public BukkitTask runTask(final @NotNull Plugin plugin) { + if (task != null) throw new IllegalStateException("This task has already started"); + + return task = plugin.getServer().getScheduler().runTask(plugin, this); + } + + @Override + @Synchronized("taskAccessLock") + public BukkitTask runTaskAsynchronously(final @NotNull Plugin plugin) { + if (task != null) throw new IllegalStateException("This task has already started"); + + return task = plugin.getServer().getScheduler().runTaskAsynchronously(plugin, this); + } + + @Override + @Synchronized("taskAccessLock") + public BukkitTask runTaskLater(final @NotNull Plugin plugin, final long delay) { + if (task != null) throw new IllegalStateException("This task has already started"); + + return task = plugin.getServer().getScheduler().runTaskLater(plugin, this, delay); + } + + @Override + @Synchronized("taskAccessLock") + public BukkitTask runTaskLaterAsynchronously(final @NotNull Plugin plugin, final long delay) { + if (task != null) throw new IllegalStateException("This task has already started"); + + return task = plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, this, delay); + } + + @Override + @Synchronized("taskAccessLock") + public BukkitTask runTaskTimer(final @NotNull Plugin plugin, final long delay, final long period) { + if (task != null) throw new IllegalStateException("This task has already started"); + + return task = plugin.getServer().getScheduler().runTaskTimer(plugin, this, delay, period); + } + + @Override + @Synchronized("taskAccessLock") + public BukkitTask runTaskTimerAsynchronously(final @NotNull Plugin plugin, final long delay, final long period) { + if (task != null) throw new IllegalStateException("This task has already started"); + + return task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, this, delay, period); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/CancellingBukkitRunnable.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/CancellingBukkitRunnable.java new file mode 100644 index 000000000..58aec20c0 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/CancellingBukkitRunnable.java @@ -0,0 +1,39 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.task; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import org.jetbrains.annotations.NotNull; + +import java.util.function.BooleanSupplier; + +/** + * A {@link ru.progrm_jarvis.minecraft.commons.schedule.task.SchedulerRunnable} + * which is run until its hook returns {@code false} on tick. + */ +@FieldDefaults(level = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class CancellingBukkitRunnable extends AbstractSchedulerRunnable { + + @Override + public void run() { + if (tick()) cancel(); + } + + protected abstract boolean tick(); + + public static SchedulerRunnable create(final @NonNull BooleanSupplier task) { + return new FunctionalCancellingBukkitRunnable(task); + } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + private static final class FunctionalCancellingBukkitRunnable extends CancellingBukkitRunnable { + + @NotNull BooleanSupplier task; + + @Override + protected boolean tick() { + return task.getAsBoolean(); + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/SchedulerRunnable.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/SchedulerRunnable.java new file mode 100644 index 000000000..8cf1921a8 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/SchedulerRunnable.java @@ -0,0 +1,28 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.task; + +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.NotNull; + +/** + * An abstraction duplicating behaviour of {@link org.bukkit.scheduler.BukkitRunnable} + * except for {@link org.bukkit.scheduler.BukkitRunnable#getTaskId()} method. + */ +public interface SchedulerRunnable extends Runnable { + + boolean isCancelled(); + + void cancel(); + + BukkitTask runTask(@NotNull Plugin plugin); + + BukkitTask runTaskAsynchronously(@NotNull Plugin plugin); + + BukkitTask runTaskLater(@NotNull Plugin plugin, long delay); + + BukkitTask runTaskLaterAsynchronously(@NotNull Plugin plugin, long delay); + + BukkitTask runTaskTimer(@NotNull Plugin plugin, long delay, long period); + + BukkitTask runTaskTimerAsynchronously(@NotNull Plugin plugin, long delay, long period); +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/conditional/AbstractConditionalTask.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/conditional/AbstractConditionalTask.java new file mode 100644 index 000000000..c664b4926 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/conditional/AbstractConditionalTask.java @@ -0,0 +1,41 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.task.conditional; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.NonNull; +import lombok.experimental.FieldDefaults; +import ru.progrm_jarvis.minecraft.commons.schedule.task.AbstractSchedulerRunnable; +import ru.progrm_jarvis.minecraft.commons.schedule.task.SchedulerRunnable; + +import java.util.function.BooleanSupplier; + +/** + * A task which will run until the specified condition becomes false. + */ +@FieldDefaults(level = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class AbstractConditionalTask extends AbstractSchedulerRunnable { + + protected abstract boolean tryRun(); + + @Override + public void run() { + if (!tryRun()) cancel(); + } + + public static SchedulerRunnable create(final @NonNull BooleanSupplier conditionalTask) { + return new SimpleConditionalTask(conditionalTask); + } + + @AllArgsConstructor(access = AccessLevel.PRIVATE) + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + private static final class SimpleConditionalTask extends AbstractConditionalTask { + + @NonNull BooleanSupplier conditionalTask; + + @Override + protected boolean tryRun() { + return conditionalTask.getAsBoolean(); + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/initializer/BukkitTaskInitializer.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/initializer/BukkitTaskInitializer.java new file mode 100644 index 000000000..e48ed1398 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/initializer/BukkitTaskInitializer.java @@ -0,0 +1,13 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.task.initializer; + +import org.bukkit.scheduler.BukkitTask; +import ru.progrm_jarvis.minecraft.commons.util.shutdown.Shutdownable; + +@FunctionalInterface +public interface BukkitTaskInitializer extends Shutdownable { + + BukkitTask initialize(); + + @Override + default void shutdown() {} +} diff --git a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/task/initializer/BukkitTaskInitializers.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/initializer/BukkitTaskInitializers.java similarity index 56% rename from scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/task/initializer/BukkitTaskInitializers.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/initializer/BukkitTaskInitializers.java index 1dd54e6ac..906c27e79 100644 --- a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/task/initializer/BukkitTaskInitializers.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/initializer/BukkitTaskInitializers.java @@ -1,4 +1,4 @@ -package ru.progrm_jarvis.minecraft.schedulerutils.task.initializer; +package ru.progrm_jarvis.minecraft.commons.schedule.task.initializer; import lombok.AccessLevel; import lombok.NonNull; @@ -6,29 +6,28 @@ import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; import lombok.experimental.UtilityClass; -import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; +import ru.progrm_jarvis.minecraft.commons.schedule.task.SchedulerRunnable; @UtilityClass public class BukkitTaskInitializers { - public BukkitTaskInitializer createTaskInitializer(@NonNull final Plugin plugin, final boolean async, - @NonNull final Runnable runnable) { + public BukkitTaskInitializer createTaskInitializer(final @NonNull Plugin plugin, final boolean async, + final @NonNull Runnable runnable) { return new AbstractBukkitTaskInitializer() { @Override public BukkitTask init() { return async - ? Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable) - : Bukkit.getScheduler().runTask(plugin, runnable); + ? plugin.getServer().getScheduler().runTaskAsynchronously(plugin, runnable) + : plugin.getServer().getScheduler().runTask(plugin, runnable); } }; } - public BukkitTaskInitializer createTaskInitializer(@NonNull final Plugin plugin, final boolean async, - @NonNull final BukkitRunnable runnable) { + public BukkitTaskInitializer createTaskInitializer(final @NonNull Plugin plugin, final boolean async, + final @NonNull SchedulerRunnable runnable) { return new AbstractBukkitTaskInitializer() { @Override @@ -38,23 +37,23 @@ public BukkitTask init() { }; } - public BukkitTaskInitializer createTimerTaskInitializer(@NonNull final Plugin plugin, final boolean async, + public BukkitTaskInitializer createTimerTaskInitializer(final @NonNull Plugin plugin, final boolean async, final long delay, final long period, - @NonNull final Runnable runnable) { + final @NonNull Runnable runnable) { return new AbstractBukkitTaskInitializer() { @Override public BukkitTask init() { return task = async - ? Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, runnable, delay, period) - : Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period); + ? plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, runnable, delay, period) + : plugin.getServer().getScheduler().runTaskTimer(plugin, runnable, delay, period); } }; } - public BukkitTaskInitializer createTimerTaskInitializer(@NonNull final Plugin plugin, final boolean async, + public BukkitTaskInitializer createTimerTaskInitializer(final @NonNull Plugin plugin, final boolean async, final long delay, final long period, - @NonNull final BukkitRunnable runnable) { + final @NonNull SchedulerRunnable runnable) { return new AbstractBukkitTaskInitializer() { @Override @@ -66,23 +65,23 @@ public BukkitTask init() { }; } - public BukkitTaskInitializer createDelayedTaskInitializer(@NonNull final Plugin plugin, final boolean async, + public BukkitTaskInitializer createDelayedTaskInitializer(final @NonNull Plugin plugin, final boolean async, final long delay, - @NonNull final Runnable runnable) { + final @NonNull Runnable runnable) { return new AbstractBukkitTaskInitializer() { @Override public BukkitTask init() { return task = async - ? Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, runnable, delay) - : Bukkit.getScheduler().runTaskLater(plugin, runnable, delay); + ? plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, runnable, delay) + : plugin.getServer().getScheduler().runTaskLater(plugin, runnable, delay); } }; } - public BukkitTaskInitializer createDelayedTaskInitializer(@NonNull final Plugin plugin, final boolean async, + public BukkitTaskInitializer createDelayedTaskInitializer(final @NonNull Plugin plugin, final boolean async, final long delay, - @NonNull final BukkitRunnable runnable) { + final @NonNull SchedulerRunnable runnable) { return new AbstractBukkitTaskInitializer() { @@ -97,7 +96,7 @@ public BukkitTask init() { @RequiredArgsConstructor(access = AccessLevel.PROTECTED) @FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) - private abstract class AbstractBukkitTaskInitializer implements BukkitTaskInitializer { + private abstract static class AbstractBukkitTaskInitializer implements BukkitTaskInitializer { @NonFinal BukkitTask task; diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/timer/AbstractBukkitTimer.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/timer/AbstractBukkitTimer.java new file mode 100644 index 000000000..0d19daca9 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/timer/AbstractBukkitTimer.java @@ -0,0 +1,66 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.task.timer; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.NonNull; +import lombok.experimental.FieldDefaults; +import org.jetbrains.annotations.NotNull; +import ru.progrm_jarvis.minecraft.commons.schedule.task.AbstractSchedulerRunnable; +import ru.progrm_jarvis.minecraft.commons.schedule.task.SchedulerRunnable; + +/** + * A {@link ru.progrm_jarvis.minecraft.commons.schedule.task.SchedulerRunnable} + * which is run for a specified amount of times after which it is cancelled. + */ +@FieldDefaults(level = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class AbstractBukkitTimer extends AbstractSchedulerRunnable { + + long counter; + + @Override + public void run() { + if (--counter == 0) { + cancelTask(); + onOver(); + } + onTick(); + } + + protected final void cancelTask() { + super.cancel(); + } + + @Override + public void cancel() { + cancelTask(); + onAbort(); + } + + protected void onTick() {} + + protected void onAbort() {} + + protected void onOver() {} + + public static SchedulerRunnable create(final @NonNull Runnable task, final long counter) { + return new SimpleBukkitTimer(task, counter); + } + + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + private static final class SimpleBukkitTimer extends AbstractBukkitTimer { + + @NotNull Runnable task; + + public SimpleBukkitTimer(final @NotNull Runnable task, + final long counter) { + super(counter); + this.task = task; + } + + @Override + protected void onTick() { + task.run(); + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/timer/AbstractCallbackTimer.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/timer/AbstractCallbackTimer.java new file mode 100644 index 000000000..f56f86f78 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/timer/AbstractCallbackTimer.java @@ -0,0 +1,70 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.task.timer; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.NonNull; +import lombok.experimental.FieldDefaults; +import org.jetbrains.annotations.NotNull; +import ru.progrm_jarvis.minecraft.commons.schedule.task.AbstractSchedulerRunnable; +import ru.progrm_jarvis.minecraft.commons.schedule.task.SchedulerRunnable; + +import java.util.function.LongConsumer; + +/** + * A {@link ru.progrm_jarvis.minecraft.commons.schedule.task.SchedulerRunnable} + * which is run for a specified amount of times after which it is cancelled. + */ +@FieldDefaults(level = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class AbstractCallbackTimer extends AbstractSchedulerRunnable { + + long counter; + + @Override + public void run() { + final long value; + + if ((value = --counter) == 0) { + cancelTask(); + onOver(); + } + onTick(value); + } + + protected final void cancelTask() { + super.cancel(); + } + + @Override + public void cancel() { + cancelTask(); + onAbort(); + } + + protected void onTick(long counter) {} + + protected void onAbort() {} + + protected void onOver() {} + + public static SchedulerRunnable create(final @NonNull LongConsumer task, final long counter) { + return new SimpleCallbackTimer(task, counter); + } + + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + private static final class SimpleCallbackTimer extends AbstractCallbackTimer { + + @NotNull LongConsumer task; + + public SimpleCallbackTimer(final @NotNull LongConsumer task, + final long counter) { + super(counter); + this.task = task; + } + + @Override + protected void onTick(final long counter) { + task.accept(counter); + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/timer/AbstractConcurrentBukkitTimer.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/timer/AbstractConcurrentBukkitTimer.java new file mode 100644 index 000000000..d96cbf4a3 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/timer/AbstractConcurrentBukkitTimer.java @@ -0,0 +1,72 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.task.timer; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.NonNull; +import lombok.experimental.FieldDefaults; +import org.jetbrains.annotations.NotNull; +import ru.progrm_jarvis.minecraft.commons.schedule.task.AbstractSchedulerRunnable; +import ru.progrm_jarvis.minecraft.commons.schedule.task.SchedulerRunnable; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * A {@link ru.progrm_jarvis.minecraft.commons.schedule.task.SchedulerRunnable} + * which is run for a specified amount of times after which it is cancelled. + */ +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +public abstract class AbstractConcurrentBukkitTimer extends AbstractSchedulerRunnable { + + @NonNull AtomicLong counter; + + @Override + public void run() { + if (counter.decrementAndGet() == 0) { + cancelTask(); + onOver(); + } + tick(); + } + + protected final void cancelTask() { + super.cancel(); + } + + @Override + public void cancel() { + cancelTask(); + onAbort(); + } + + protected void tick() {} + + protected void onAbort() {} + + protected void onOver() {} + + public static SchedulerRunnable create(final @NonNull Runnable task, final long counter) { + return new SimpleConcurrentBukkitTimer(task, new AtomicLong(counter)); + } + + public static SchedulerRunnable create(final @NonNull Runnable task, final @NonNull AtomicLong counter) { + return new SimpleConcurrentBukkitTimer(task, counter); + } + + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + private static final class SimpleConcurrentBukkitTimer extends AbstractConcurrentBukkitTimer { + + @NotNull Runnable task; + + public SimpleConcurrentBukkitTimer(final @NotNull Runnable task, + final @NotNull AtomicLong counter) { + super(counter); + this.task = task; + } + + @Override + protected void tick() { + task.run(); + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/timer/AbstractConcurrentCallbackTimer.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/timer/AbstractConcurrentCallbackTimer.java new file mode 100644 index 000000000..677129ee3 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/schedule/task/timer/AbstractConcurrentCallbackTimer.java @@ -0,0 +1,73 @@ +package ru.progrm_jarvis.minecraft.commons.schedule.task.timer; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import org.jetbrains.annotations.NotNull; +import ru.progrm_jarvis.minecraft.commons.schedule.task.AbstractSchedulerRunnable; +import ru.progrm_jarvis.minecraft.commons.schedule.task.SchedulerRunnable; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.LongConsumer; + +/** + * A {@link ru.progrm_jarvis.minecraft.commons.schedule.task.SchedulerRunnable} + * which is run for a specified amount of times after which it is cancelled. + */ +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +public abstract class AbstractConcurrentCallbackTimer extends AbstractSchedulerRunnable { + + @NonNull AtomicLong counter; + + @Override + public void run() { + final long value; + + if ((value = counter.decrementAndGet()) == 0) { + cancelTask(); + onOver(); + } + onTick(value); + } + + protected final void cancelTask() { + super.cancel(); + } + + @Override + public void cancel() { + cancelTask(); + onAbort(); + } + + protected void onTick(long counter) {} + + protected void onAbort() {} + + protected void onOver() {} + + public static SchedulerRunnable create(final @NonNull LongConsumer task, final long counter) { + return new SimpleConcurrentCallbackTimer(task, new AtomicLong(counter)); + } + + public static SchedulerRunnable create(final @NonNull LongConsumer task, final @NonNull AtomicLong counter) { + return new SimpleConcurrentCallbackTimer(task, counter); + } + + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + private static final class SimpleConcurrentCallbackTimer extends AbstractConcurrentCallbackTimer { + + @NotNull LongConsumer task; + + public SimpleConcurrentCallbackTimer(final @NotNull LongConsumer task, + final @NotNull AtomicLong counter) { + super(counter); + this.task = task; + } + + @Override + protected void onTick(final long counter) { + task.accept(counter); + } + } +} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/BitwiseUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/BitwiseUtil.java similarity index 82% rename from commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/BitwiseUtil.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/BitwiseUtil.java index 158e3a729..0dcabb13e 100644 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/BitwiseUtil.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/BitwiseUtil.java @@ -10,23 +10,23 @@ public class BitwiseUtil { /** - * Number of bits in a {@link byte} + * Number of bits in a {@code byte} */ private static final int BYTE_BITS = 8, /** - * Number of bits in a {@link short} + * Number of bits in a {@code short} */ SHORT_BITS = 16, /** - * Number of bits in a {@link int} + * Number of bits in a {@code int} */ INT_BITS = 32, /** - * Number of bits in a {@link long} + * Number of bits in a {@code long} */ LONG_BITS = 64, /** - * Number of bits in a {@link char} + * Number of bits in a {@code char} */ CHAR_BITS = 16; @@ -173,4 +173,24 @@ public char implicate(char num1, final char num2) { return num1; } + + /** + * Converts the unsigned {@code int} to a 8-bit ({@code byte}) representation. + * + * @param unsignedInt integer whose least significant 8 bits are to be stored in a byte + * @return unsigned integer's least significant 8 bits in a single byte + */ + public byte unsignedIntToByte(final int unsignedInt) { + return (byte) unsignedInt; + } + + /** + * Converts the 8-bit ({@code byte}) value to an unsigned int representation. + * + * @param byteValue byte value whose bits will be used as the trailing bits of the resulting integer + * @return an integer value consisting of 24 foremost 0s and 8 bits of the specified byte + */ + public int byteToUnsignedInt(final byte byteValue) { + return byteValue & 0xFF; + } } diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/ItemStackUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/ItemStackUtil.java new file mode 100644 index 000000000..06c93b964 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/ItemStackUtil.java @@ -0,0 +1,56 @@ +package ru.progrm_jarvis.minecraft.commons.util; + +import lombok.experimental.UtilityClass; +import lombok.var; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Utility for {@link ItemStack}-related functionality. + */ +@UtilityClass +public class ItemStackUtil { + + /** + * Deeply copies the given array of {@link ItemStack}s. + * + * @param items array to be deeply copied + * @return deep copy of the given items array + * + * @implNote {@link ItemStack#clone()} is used for cloning + */ + public @NotNull ItemStack @Nullable [] deepCopy(final @NotNull ItemStack[] items) { + final int length; + final ItemStack[] copy = new ItemStack[length = items.length]; + for (var i = 0; i < length; i++) copy[i] = items[i].clone(); + + return copy; + } + + /** + * Tests if the given item is empty. + * + * @param item item to check for emptiness + * @return {@code true} if and only if the item is {@code null} + * or of empty type (i.e. {@link Material#AIR}) + */ + @Contract("null -> true") + public boolean isEmpty(final @Nullable ItemStack item) { + return item == null || item.getType() == Material.AIR; + } + + /** + * Tests if the given item is not empty. + * + * @param item item to check for non-emptiness + * @return {@code false} if and only if the item is not {@code null} + * and not of empty type (i.e. {@link Material#AIR}) + */ + @Contract("null -> false") + public boolean isNotEmpty(final @Nullable ItemStack item) { + return item != null && item.getType() != Material.AIR; + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/LocationUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/LocationUtil.java new file mode 100644 index 000000000..300d58067 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/LocationUtil.java @@ -0,0 +1,68 @@ +package ru.progrm_jarvis.minecraft.commons.util; + +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import org.bukkit.Location; +import org.bukkit.block.BlockFace; +import org.jetbrains.annotations.NotNull; + +@UtilityClass +public class LocationUtil { + + public void applyOffset(@NotNull Location location, + final double dx, final double dy, final double dz, + final float dYaw, final float dPitch) { + location.add(dx, dy, dz).setYaw(location.getYaw() + dYaw); + location.setPitch(location.getPitch() + dPitch); + } + + public @NotNull Location withOffset(@NotNull Location location, + final double dx, final double dy, final double dz, + final float dYaw, final float dPitch) { + (location = location.clone().add(dx, dy, dz)).setYaw(location.getYaw() + dYaw); + location.setPitch(location.getPitch() + dPitch); + + return location; + } + + public double getDistanceSquared(final double dx, final double dy, final double dz) { + return dx * dx + dy * dy + dz * dz; + } + + public double getDistanceSquared(final double x1, final double y1, final double z1, + final double x2, final double y2, final double z2) { + return getDistanceSquared(x2 - x1, y2 - y1, z2 - z1); + } + + /** + *

Gets the squared distance between the locations.

+ *

This is a world-ignoring analog of {@link Location#distanceSquared(Location)}.

+ * + * @param location1 first location + * @param location2 second location + * @return the squared distance between the locations + */ + public double getDistanceSquared(final Location location1, final Location location2) { + return getDistanceSquared( + location2.getX() - location1.getX(), + location2.getY() - location1.getY(), + location2.getZ() - location1.getZ() + ); + } + + /** + *

Gets the distance between the locations.

+ *

This is a world-ignoring analog of {@link Location#distance(Location)}.

+ * + * @param location1 first location + * @param location2 second location + * @return the distance between the locations + */ + public double getDistance(final Location location1, final Location location2) { + return Math.sqrt(getDistanceSquared(location1, location2)); + } + + public Location nearestLocation(final @NonNull Location location, final @NonNull BlockFace blockFace) { + return location.add(blockFace.getModX(), blockFace.getModY(), blockFace.getModZ()); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/ReflectionUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/ReflectionUtil.java new file mode 100644 index 000000000..2fc662dca --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/ReflectionUtil.java @@ -0,0 +1,21 @@ +package ru.progrm_jarvis.minecraft.commons.util; + +import lombok.NonNull; +import lombok.experimental.UtilityClass; + +/** + * Utility containing stuff not available in Reflector yet. + */ +@UtilityClass +public class ReflectionUtil { + + public boolean isClassAvailable(final @NonNull String className) { + try { + Class.forName(className); + + return true; + } catch (final ClassNotFoundException e) { + return false; + } + } +} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/SystemPropertyUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/SystemPropertyUtil.java similarity index 79% rename from commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/SystemPropertyUtil.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/SystemPropertyUtil.java index 7b505b01c..bdb613468 100644 --- a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/SystemPropertyUtil.java +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/SystemPropertyUtil.java @@ -22,12 +22,12 @@ public class SystemPropertyUtil { * @param type of the resulting property value * @return found property transformed to required type or the default value if the property is unset */ - public T getSystemProperty(@NonNull final String propertyName, - @NonNull final Function transformer, - @NonNull final Supplier defaultValueSupplier) { + public T getSystemProperty(final @NonNull String propertyName, + final @NonNull Function transformer, + final @NonNull Supplier defaultValueSupplier) { val property = System.getProperty(propertyName); - return property == null ? defaultValueSupplier.get() : transformer.apply(property); + return property == null ? defaultValueSupplier.get() : transformer.apply(property); } /** @@ -40,14 +40,14 @@ public T getSystemProperty(@NonNull final String propertyName, * @param type of the resulting property value * @return found property transformed to required type or the default value if the property is unset */ - public T getSystemProperty(@NonNull final String propertyName, - @NonNull final Function transformer, - @NonNull final T defaultValue) { + public T getSystemProperty(final @NonNull String propertyName, + final @NonNull Function transformer, + final @NonNull T defaultValue) { return getSystemProperty(propertyName, transformer, (Supplier) () -> defaultValue); } /** - * Gets the system property of type {@link boolean} by the name specified + * Gets the system property of type {@code boolean} by the name specified * and using the default value specified if the property is unset. * * @param propertyName name of the property @@ -55,15 +55,15 @@ public T getSystemProperty(@NonNull final String propertyName, * @return found property transformed to required type * or the default value if the property is unset */ - public boolean getSystemPropertyBoolean(@NonNull final String propertyName, - @NonNull final BooleanSupplier defaultValueSupplier) { + public boolean getSystemPropertyBoolean(final @NonNull String propertyName, + final @NonNull BooleanSupplier defaultValueSupplier) { val property = System.getProperty(propertyName); return property == null ? defaultValueSupplier.getAsBoolean() : Boolean.parseBoolean(property); } /** - * Gets the system property of type {@link boolean} by the name specified + * Gets the system property of type {@code boolean} by the name specified * and using the default value specified if the property is unset. * * @param propertyName name of the property @@ -71,12 +71,12 @@ public boolean getSystemPropertyBoolean(@NonNull final String propertyName, * @return found property transformed to required type * or the default value if the property is unset */ - public boolean getSystemPropertyBoolean(@NonNull final String propertyName, final boolean defaultValue) { + public boolean getSystemPropertyBoolean(final @NonNull String propertyName, final boolean defaultValue) { return getSystemPropertyBoolean(propertyName, () -> defaultValue); } /** - * Gets the system property of type {@link int} by the name specified + * Gets the system property of type {@code int} by the name specified * and using the default value specified if the property is unset or is not a valid number. * * @param propertyName name of the property @@ -84,8 +84,8 @@ public boolean getSystemPropertyBoolean(@NonNull final String propertyName, fina * @return found property transformed to required type * or the default value if the property is unset or is not a valid number */ - public int getSystemPropertyInt(@NonNull final String propertyName, - @NonNull final IntSupplier defaultValueSupplier) { + public int getSystemPropertyInt(final @NonNull String propertyName, + final @NonNull IntSupplier defaultValueSupplier) { val property = System.getProperty(propertyName); if (property == null) return defaultValueSupplier.getAsInt(); @@ -97,7 +97,7 @@ public int getSystemPropertyInt(@NonNull final String propertyName, } /** - * Gets the system property of type {@link int} by the name specified + * Gets the system property of type {@code int} by the name specified * and using the default value specified if the property is unset or is not a valid number. * * @param propertyName name of the property @@ -105,12 +105,12 @@ public int getSystemPropertyInt(@NonNull final String propertyName, * @return found property transformed to required type * or the default value if the property is unset or is not a valid number */ - public int getSystemPropertyInt(@NonNull final String propertyName, final int defaultValue) { + public int getSystemPropertyInt(final @NonNull String propertyName, final int defaultValue) { return getSystemPropertyInt(propertyName, () -> defaultValue); } /** - * Gets the system property of type {@link long} by the name specified + * Gets the system property of type {@code long} by the name specified * and using the default value specified if the property is unset or is not a valid number. * * @param propertyName name of the property @@ -118,8 +118,8 @@ public int getSystemPropertyInt(@NonNull final String propertyName, final int de * @return found property transformed to required type * or the default value if the property is unset or is not a valid number */ - public long getSystemPropertyLong(@NonNull final String propertyName, - @NonNull final LongSupplier defaultValueSupplier) { + public long getSystemPropertyLong(final @NonNull String propertyName, + final @NonNull LongSupplier defaultValueSupplier) { val property = System.getProperty(propertyName); if (property == null) return defaultValueSupplier.getAsLong(); @@ -131,7 +131,7 @@ public long getSystemPropertyLong(@NonNull final String propertyName, } /** - * Gets the system property of type {@link long} by the name specified + * Gets the system property of type {@code long} by the name specified * and using the default value specified if the property is unset or is not a valid number. * * @param propertyName name of the property @@ -139,12 +139,12 @@ public long getSystemPropertyLong(@NonNull final String propertyName, * @return found property transformed to required type * or the default value if the property is unset or is not a valid number */ - public long getSystemPropertyLong(@NonNull final String propertyName, final long defaultValue) { + public long getSystemPropertyLong(final @NonNull String propertyName, final long defaultValue) { return getSystemPropertyLong(propertyName, () -> defaultValue); } /** - * Gets the system property of type {@link double} by the name specified + * Gets the system property of type {@code double} by the name specified * and using the default value specified if the property is unset or is not a valid number. * * @param propertyName name of the property @@ -152,8 +152,8 @@ public long getSystemPropertyLong(@NonNull final String propertyName, final long * @return found property transformed to required type * or the default value if the property is unset or is not a valid number */ - public double getSystemPropertyDouble(@NonNull final String propertyName, - @NonNull final DoubleSupplier defaultValueSupplier) { + public double getSystemPropertyDouble(final @NonNull String propertyName, + final @NonNull DoubleSupplier defaultValueSupplier) { val property = System.getProperty(propertyName); if (property == null) return defaultValueSupplier.getAsDouble(); @@ -165,7 +165,7 @@ public double getSystemPropertyDouble(@NonNull final String propertyName, } /** - * Gets the system property of type {@link double} by the name specified + * Gets the system property of type {@code double} by the name specified * and using the default value specified if the property is unset or is not a valid number. * * @param propertyName name of the property @@ -173,7 +173,7 @@ public double getSystemPropertyDouble(@NonNull final String propertyName, * @return found property transformed to required type * or the default value if the property is unset or is not a valid number */ - public double getSystemPropertyDouble(@NonNull final String propertyName, final double defaultValue) { + public double getSystemPropertyDouble(final @NonNull String propertyName, final double defaultValue) { return getSystemPropertyDouble(propertyName, () -> defaultValue); } } diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/chat/ChatComponentUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/chat/ChatComponentUtil.java new file mode 100644 index 000000000..38cd7b213 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/chat/ChatComponentUtil.java @@ -0,0 +1,55 @@ +package ru.progrm_jarvis.minecraft.commons.util.chat; + +import com.comphenix.protocol.wrappers.WrappedChatComponent; +import com.google.gson.*; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.experimental.UtilityClass; + +import java.lang.reflect.Type; + +/** + * Utility for functions related to chat-components. + */ +@UtilityClass +public class ChatComponentUtil { + + /** + * Gets an instance of {@link WrappedChatComponentGsonSerializer} + * capable of serializing and deserializing {@link WrappedChatComponent} + * which may be used in {@link GsonBuilder#registerTypeAdapter(Type, Object)}. + * + * @return instance of {@link WrappedChatComponentGsonSerializer} for {@link WrappedChatComponent} (de)serialization + */ + public static WrappedChatComponentGsonSerializer wrappedChatComponentGsonSerializer() { + return WrappedChatComponentGsonSerializer.INSTANCE; + } + + /** + * {@link JsonSerializer} and {@link JsonDeserializer} + * for use with {@link GsonBuilder#registerTypeAdapter(Type, Object)}. + */ + @NoArgsConstructor(access = AccessLevel.PROTECTED) + public static class WrappedChatComponentGsonSerializer + implements JsonSerializer, JsonDeserializer { + + /** + * Singleton instance of {@link WrappedChatComponentGsonSerializer}. + */ + private static final WrappedChatComponentGsonSerializer INSTANCE = new WrappedChatComponentGsonSerializer(); + + @Override + public WrappedChatComponent deserialize(final JsonElement json, final Type typeOfT, + final JsonDeserializationContext context) + throws JsonParseException { + if (json.isJsonObject()) return WrappedChatComponent.fromJson(json.toString()); + return WrappedChatComponent.fromText(json.getAsString()); + } + + @Override + public JsonElement serialize(final WrappedChatComponent src, final Type typeOfSrc, + final JsonSerializationContext context) { + return context.serialize(context.serialize(src.getJson())); + } + } +} diff --git a/commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/hack/PreSuperCheck.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/hack/PreSuperCheck.java similarity index 100% rename from commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/hack/PreSuperCheck.java rename to minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/hack/PreSuperCheck.java diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/image/ColorUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/image/ColorUtil.java new file mode 100644 index 000000000..ee2a66ebf --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/image/ColorUtil.java @@ -0,0 +1,134 @@ +package ru.progrm_jarvis.minecraft.commons.util.image; + +import lombok.experimental.UtilityClass; +import lombok.val; +import org.jetbrains.annotations.Contract; + +import static java.lang.Math.min; + +/** + * Utilities related to standard {@code int}-ARGB representation of colors. + */ +@UtilityClass +public class ColorUtil { + + /** + * Length of bitwise {@code int} shift for accessing alpha channel value of standard {@code int}-ARGB. + */ + public static final byte ALPHA_CHANNEL = 24, + /** + * Length of bitwise {@code int} shift for accessing red color channel value of standard {@code int}-ARGB. + */ + RED_CHANNEL = 16, + /** + * Length of bitwise {@code int} shift for accessing green color channel value of standard {@code int}-ARGB. + */ + GREEN_CHANNEL = 8, + /** + * Length of bitwise {@code int} shift for accessing blue color channel value of standard {@code int}-ARGB. + */ + BLUE_CHANNEL = 0; + + /** + * Gets the alpha channel value for the specified ARGB {@code int}. + * + * @param rgb RGB encoded as a single integer + * @return alpha channel value + */ + @Contract(pure = true) + public static int alpha(final int rgb) { + return rgb >> ALPHA_CHANNEL & 0xFF; + } + + /** + * Gets the red color channel value for the specified ARGB {@code int}. + * + * @param rgb RGB encoded as a single integer + * @return red color channel value + */ + @Contract(pure = true) + public static int red(final int rgb) { + return rgb >> RED_CHANNEL & 0xFF; + } + + /** + * Gets the green color channel value for the specified ARGB {@code int}. + * + * @param rgb RGB encoded as a single integer + * @return green color channel value + */ + @Contract(pure = true) + public static int green(final int rgb) { + return rgb >> GREEN_CHANNEL & 0xFF; + } + + /** + * Gets the blue color channel value for the specified ARGB {@code int}. + * + * @param rgb RGB encoded as a single integer + * @return blue color channel value + */ + @Contract(pure = true) + public static int blue(final int rgb) { + return rgb >> BLUE_CHANNEL & 0xFF; + } + + /** + * Transforms 4 ARGB channels to a single {@code int}. + * + * @param alpha alpha color channel (between {@code 0} and {@code 255}) + * @param red red color channel (between {@code 0} and {@code 255}) + * @param green green color channel (between {@code 0} and {@code 255}) + * @param blue blue color channel (between {@code 0} and {@code 255}) + * @return color as a single RGB {@code int} + */ + @Contract(pure = true) + public static int toArgb(final int alpha, final int red, final int green, final int blue) { + return (alpha << ALPHA_CHANNEL) | (red << RED_CHANNEL) | (green << GREEN_CHANNEL) | (blue << BLUE_CHANNEL); + } + + /** + * Transforms 3 color channels channels to a single ARGB {@code int} with no transparency. + * + * @param red red color channel (between {@code 0} and {@code 255}) + * @param green green color channel (between {@code 0} and {@code 255}) + * @param blue blue color channel (between {@code 0} and {@code 255}) + * @return color as a single RGB {@code int} + */ + @Contract(pure = true) + public static int toArgb(final int red, final int green, final int blue) { + return toArgb(0xFF, red, green, blue); + } + + public int blendColorsNoAlpha(final int backgroundColor, final int foregroundColor) { + return toArgb( + (red(foregroundColor) + red(backgroundColor)) / 2, + (green(foregroundColor) + green(backgroundColor)) / 2, + (blue(foregroundColor) + blue(backgroundColor)) / 2 + ); + } + + /** + * Blends two ARGB colors into one respecting alphas. + * @param backgroundColor background color as a standard {@code int}-ARGB + * @param foregroundColor foreground color as a standard {@code int}-ARGB + * @return standard {@code int}-ARGB color being the result of color-bleeding + */ + @Contract(pure = true) + public int blendColors(final int backgroundColor, final int foregroundColor) { + val foregroundAlpha = alpha(foregroundColor); + if (foregroundAlpha == 0x0) return backgroundColor; + if (foregroundAlpha == 0xFF) return foregroundColor; + + val backgroundAlpha = alpha(backgroundColor); + if (backgroundAlpha == foregroundAlpha) return foregroundAlpha + | blendColorsNoAlpha(backgroundColor, foregroundColor) & 0xFFFFFF; + + return toArgb( + min(backgroundAlpha + foregroundAlpha, 255), + min((red(backgroundColor) * backgroundAlpha + red(foregroundColor) * foregroundAlpha) / 255, 255), + min((green(backgroundColor) * backgroundAlpha + green(foregroundColor) * foregroundAlpha) / 255, 255), + min((blue(backgroundColor) * backgroundAlpha + blue(foregroundColor) * foregroundAlpha) / 255, 255) + ); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/image/ImageUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/image/ImageUtil.java new file mode 100644 index 000000000..6af3a482d --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/image/ImageUtil.java @@ -0,0 +1,82 @@ +package ru.progrm_jarvis.minecraft.commons.util.image; + +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import lombok.val; +import lombok.var; +import org.jetbrains.annotations.Contract; + +import java.awt.image.BufferedImage; + +import static java.lang.Math.min; + +/** + * Utility for {@link java.awt} related image stuff. + */ +@UtilityClass +public class ImageUtil { + + /** + * Clones the BufferedImage into a new one. + * + * @param image image to clone + * @return cloned image + */ + public BufferedImage clone(final @NonNull BufferedImage image) { + val clonedImage = new BufferedImage(image.getWidth(), image.getHeight(), image.getType()); + val graphics = clonedImage.getGraphics(); + graphics.drawImage(image, 0, 0, null); + + return clonedImage; + } + + /** + * Merge multiple images into the first one respecting alpha channel. + * + * @param background image to which to merge the foregrounds + * @param foregrounds images to merge to the foreground + * @return the given background with foregrounds merged + * + * @see #clone(BufferedImage) should be used to keep your source image unmodified + */ + @Contract("null, _ -> fail; _, null -> fail; !null, _ -> param1") + public BufferedImage merge(final @NonNull BufferedImage background, final @NonNull BufferedImage... foregrounds) { + final int width = background.getWidth(), height = background.getHeight(); + val graphics = background.getGraphics(); + + for (val foreground : foregrounds) graphics.drawImage( + foreground.getSubimage(0, 0, min(width, foreground.getWidth()), min(height, foreground.getHeight())), + 0, 0, null + ); + + return background; + } + + /** + * Merge multiple images using into the first one using {@link ColorUtil#blendColors(int, int)} . + * + * @param background image to which to merge the foregrounds + * @param foregrounds images to merge to the foreground + * @return the given background with foregrounds merged + * + * @implNote current implementation is based on pixel-by-pixel operation + * + * @see #clone(BufferedImage) should be used to keep your source image unmodified + * @see ColorUtil#blendColors(int, int) used color blending algorithm + */ + @Contract("null, _ -> fail; _, null -> fail; !null, _ -> param1") + public BufferedImage mergeSharp(final @NonNull BufferedImage background, + final @NonNull BufferedImage... foregrounds) { + final int width = background.getWidth(), height = background.getHeight(); + + for (val foreground : foregrounds) { + final int foregroundWidth = min(width, foreground.getWidth()), + foregroundHeight = min(height, foreground.getHeight()); + + for (var x = 0; x < foregroundWidth; x++) for (var y = 0; y < foregroundHeight; y++) background + .setRGB(x, y, ColorUtil.blendColors(background.getRGB(x, y), foreground.getRGB(x, y))); + } + + return background; + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/io/FileUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/io/FileUtil.java new file mode 100644 index 000000000..913fff897 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/io/FileUtil.java @@ -0,0 +1,36 @@ +package ru.progrm_jarvis.minecraft.commons.util.io; + +import lombok.NonNull; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import lombok.val; + +import java.io.File; +import java.nio.file.Files; + +/** + * Utilities for common + */ +@UtilityClass +public class FileUtil { + + /** + * Creates the file if it doesn't exists. + * + * @param file file to make exist if possible + * @return the file specified + * + * @apiNote this attempts to create a file and its parent directory only when needed + */ + @SneakyThrows + public File makeSureExists(final @NonNull File file) { + if (!file.isFile()) { + val parent = file.getParentFile(); + if (parent != null && !parent.isDirectory()) Files.createDirectories(parent.toPath()); + + Files.createFile(file.toPath()); + } + + return file; + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/shutdown/ObjectAlreadyShutDownException.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/shutdown/ObjectAlreadyShutDownException.java new file mode 100644 index 000000000..befce56e1 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/shutdown/ObjectAlreadyShutDownException.java @@ -0,0 +1,37 @@ +package ru.progrm_jarvis.minecraft.commons.util.shutdown; + +import lombok.NonNull; +import org.jetbrains.annotations.Nullable; + +/** + * An exception which may be thrown whenever {@link Shutdownable#shutdown()} + * is called multiple times on an object which becomes unavailable after the first call. + */ +public class ObjectAlreadyShutDownException extends RuntimeException { + + /** + * Instantiates a new exception. + * + * @param message message describing the reason of the exception + */ + public ObjectAlreadyShutDownException(final @NonNull String message) { + super(message); + } + + /** + * Instantiates a new exception with default message. + */ + public ObjectAlreadyShutDownException() { + this("Object is already shut down"); + } + + /** + * Instantiates a new exception. + * + * @param object object whose {@link Shutdownable#shutdown()} was called multiple times + * although it was expected to be called only once + */ + public ObjectAlreadyShutDownException(final @Nullable Shutdownable object) { + this(object == null ? "Object is already shut down" : object + " is already shut down"); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/shutdown/ShutdownHooks.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/shutdown/ShutdownHooks.java new file mode 100644 index 000000000..84751b7d8 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/shutdown/ShutdownHooks.java @@ -0,0 +1,334 @@ +package ru.progrm_jarvis.minecraft.commons.util.shutdown; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.Nullable; +import ru.progrm_jarvis.minecraft.commons.plugin.BukkitPluginShutdownUtil; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * An object holding all shutdown hooks of an object which should be called once it should bw shut down. + * This object also guarantees that all hooks will be ran only once and repeated call + */ +public interface ShutdownHooks extends Shutdownable { + + /** + * Adds a shutdown hook. + * + * @param hook shutdown hook to add + * @return this shutdown hooks for chaining + */ + @NonNull ShutdownHooks add(@NonNull Runnable hook); + + /** + * Adds a shutdown hook. + * + * @param hookSupplier supplier to be used instantly to create a hook + * @return this shutdown hooks for chaining + * + * @apiNote supplier is called instantly, not lazily + */ + @NonNull ShutdownHooks add(@NonNull Supplier hookSupplier); + + /** + * Adds a shutdown hook. + * + * @param objectSupplier supplier to create an object which wil be shut down + * @param hookCreator function to create a hook + * @param type of the supplied object + * @return this shutdown hooks for chaining + * + * @apiNote supplier and function are called instantly, not lazily + */ + T add(@NonNull Supplier objectSupplier, @NonNull Function hookCreator); + + /** + * Removes a shutdown hook. + * + * @param hook shutdown hook to remove + * @return this shutdown hooks for chaining + */ + @NonNull ShutdownHooks remove(@NonNull Runnable hook); + + /** + * Registers these shutdown hooks as a Bukkit plugin shutdown hook. + * + * @param plugin plugin whose shutdown hook this is + * @return this shutdown hooks for chaining + */ + @NonNull ShutdownHooks registerBukkitShutdownHook(@NonNull Plugin plugin); + + /** + * Unregisters these shutdown hooks as a Bukkit plugin shutdown hook. + * + * @return this shutdown hooks for chaining + */ + @NonNull ShutdownHooks unregisterBukkitShutdownHook(); + + /** + * Calls all the hooks. + */ + @Override + void shutdown(); + + /** + * Retrieves whether or not {@link #shutdown()} was called. + * + * @return {@code true} if this was shut down and {@code false} otherwise + */ + boolean isShutDown(); + + /** + * Creates a new shutdown hook instance. + * + * @return created shutdown hook instance + */ + static ShutdownHooks create() { + return new Simple(); + } + + /** + * Creates new shutdown hook instance. + * + * @param parent object whose shutdown hooks those are + * @return created shutdown hook instance + */ + static ShutdownHooks create(final @NonNull Shutdownable parent) { + return new Simple(parent); + } + + /** + * Creates new concurrent shutdown hook instance. + * + * @return created concurrent shutdown hook instance + */ + static ShutdownHooks createConcurrent() { + return new Concurrent(); + } + + /** + * Creates new concurrent shutdown hook instance. + * + * @param parent object whose shutdown hooks those are + * @return created concurrent shutdown hook instance + */ + static ShutdownHooks createConcurrent(final @NonNull Shutdownable parent) { + return new Concurrent(parent); + } + + // equals and hashcode are specifically omitted due to object's mutability + @ToString + @FieldDefaults(level = AccessLevel.PROTECTED) + @RequiredArgsConstructor(access = AccessLevel.PROTECTED) + class Simple implements ShutdownHooks { + + final @Nullable Shutdownable parent; + final @NonNull Deque shutdownHooks = new ArrayDeque<>(); + @Getter boolean shutDown = false; + + @Nullable Plugin bukkitPlugin; + + protected Simple() { + this(null); + } + + protected void checkState() { + if (shutDown) throw new ObjectAlreadyShutDownException(parent); + } + + @Override + @NonNull public ShutdownHooks add(final @NonNull Runnable hook) { + checkState(); + + shutdownHooks.add(hook); + + return this; + } + + @Override + @NonNull public ShutdownHooks add(final @NonNull Supplier hookSupplier) { + checkState(); + + shutdownHooks.add(hookSupplier.get()); + + return this; + } + + @Override + public T add(final @NonNull Supplier objectSupplier, final @NonNull Function hookCreator) { + checkState(); + + val object = objectSupplier.get(); + shutdownHooks.add(hookCreator.apply(object)); + + return object; + } + + @Override + @NonNull public ShutdownHooks remove(final @NonNull Runnable hook) { + checkState(); + + shutdownHooks.remove(hook); + + return this; + } + + @Override + @NonNull public ShutdownHooks registerBukkitShutdownHook(final @NonNull Plugin plugin) { + checkState(); + + if (bukkitPlugin == null) { + BukkitPluginShutdownUtil.addShutdownHook(bukkitPlugin = plugin, this); + + return this; + } else { + BukkitPluginShutdownUtil.removeShutdownHook(bukkitPlugin, this); + BukkitPluginShutdownUtil.addShutdownHook(plugin, this); + } + + return this; + } + + @Override + @NonNull public ShutdownHooks unregisterBukkitShutdownHook() { + checkState(); + + if (bukkitPlugin != null) { + BukkitPluginShutdownUtil.removeShutdownHook(bukkitPlugin, this); + bukkitPlugin = null; + } + + return this; + } + + @Override + public void shutdown() { + if (shutDown) return; + + shutDown = true; + + if (bukkitPlugin != null) { + BukkitPluginShutdownUtil.removeShutdownHook(bukkitPlugin, this); + bukkitPlugin = null; + } + + for (val shutdownHook : shutdownHooks) shutdownHook.run(); + + shutdownHooks.clear(); + } + } + + // equals and hashcode are specifically omitted due to object's mutability + @ToString + @FieldDefaults(level = AccessLevel.PROTECTED) + @RequiredArgsConstructor(access = AccessLevel.PROTECTED) + class Concurrent implements ShutdownHooks { + + final @Nullable Shutdownable parent; + final @NonNull Deque shutdownHooks = new ConcurrentLinkedDeque<>(); + AtomicBoolean shutDown = new AtomicBoolean(); + + final @NonNull AtomicReference bukkitPlugin = new AtomicReference<>(); + + protected Concurrent() { + this(null); + } + + protected void checkState() { + if (shutDown.get()) throw new ObjectAlreadyShutDownException(parent); + } + + @Override + @NonNull public ShutdownHooks add(final @NonNull Runnable hook) { + checkState(); + + shutdownHooks.add(hook); + + return this; + } + + @Override + @NonNull public ShutdownHooks add(final @NonNull Supplier hookSupplier) { + checkState(); + + shutdownHooks.add(hookSupplier.get()); + + return this; + } + + @Override + public T add(final @NonNull Supplier objectSupplier, final @NonNull Function hookCreator) { + checkState(); + + val object = objectSupplier.get(); + shutdownHooks.add(hookCreator.apply(object)); + + return object; + } + + @Override + @NonNull public ShutdownHooks remove(final @NonNull Runnable hook) { + checkState(); + + shutdownHooks.remove(hook); + + return this; + } + + @Override + @NonNull public ShutdownHooks registerBukkitShutdownHook(final @NonNull Plugin plugin) { + checkState(); + + if (bukkitPlugin.compareAndSet(null, plugin)) BukkitPluginShutdownUtil.addShutdownHook(plugin, this); + else { + BukkitPluginShutdownUtil.removeShutdownHook(bukkitPlugin.get(), this); + BukkitPluginShutdownUtil.addShutdownHook(plugin, this); + } + + return this; + } + + @Override + @NonNull public ShutdownHooks unregisterBukkitShutdownHook() { + checkState(); + + val plugin = bukkitPlugin.get(); + if (plugin != null) { + BukkitPluginShutdownUtil.removeShutdownHook(plugin, this); + bukkitPlugin.compareAndSet(plugin, null); + } + + return this; + } + + @Override + public void shutdown() { + if (shutDown.compareAndSet(false, true)) { + { + val plugin = bukkitPlugin.get(); + if (plugin != null) { + BukkitPluginShutdownUtil.removeShutdownHook(plugin, this); + bukkitPlugin.set(null); + } + } + + for (val shutdownHook : shutdownHooks) shutdownHook.run(); + + shutdownHooks.clear(); + } + } + + @Override + public boolean isShutDown() { + return shutDown.get(); + } + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/shutdown/Shutdownable.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/shutdown/Shutdownable.java new file mode 100644 index 000000000..48573cbaf --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/shutdown/Shutdownable.java @@ -0,0 +1,20 @@ +package ru.progrm_jarvis.minecraft.commons.util.shutdown; + +/** + * An object which may be shutdown and should be once its usage is over. + */ +@FunctionalInterface +public interface Shutdownable extends AutoCloseable { + + /** + * Shuts this object down. + * + * @apiNote this means that the object may be unusable in future + */ + void shutdown(); + + @Override + default void close() { + shutdown(); + } +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/time/Time.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/time/Time.java new file mode 100644 index 000000000..0409862b0 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/time/Time.java @@ -0,0 +1,37 @@ +package ru.progrm_jarvis.minecraft.commons.util.time; + +import com.google.common.base.Preconditions; +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.Value; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; + +import javax.annotation.Nonnegative; +import java.util.concurrent.TimeUnit; + +/** + * A simple value consisting of {@code long} duration and its {@link TimeUnit} type. + */ +@Value +@Accessors(fluent = true) +@FieldDefaults(level = AccessLevel.PRIVATE) +public class Time { + + public Time(final long duration, final @NonNull TimeUnit unit) { + Preconditions.checkArgument(duration >= 0, "duration should be non-negative"); + + this.duration = duration; + this.unit = unit; + } + + /** + * Duration value + */ + @Nonnegative long duration; + + /** + * Unit type + */ + @NonNull TimeUnit unit; +} diff --git a/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/title/TitleUtil.java b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/title/TitleUtil.java new file mode 100644 index 000000000..46c18eb56 --- /dev/null +++ b/minecraft-commons/src/main/java/ru/progrm_jarvis/minecraft/commons/util/title/TitleUtil.java @@ -0,0 +1,264 @@ +package ru.progrm_jarvis.minecraft.commons.util.title; + +import com.comphenix.packetwrapper.WrapperPlayServerTitle; +import com.comphenix.protocol.wrappers.EnumWrappers; +import com.comphenix.protocol.wrappers.WrappedChatComponent; +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import lombok.val; +import org.bukkit.entity.Player; + +/** + * Utility for title rendering on client. + */ +@UtilityClass +public class TitleUtil { + + /** + * Packet for clearing the title + */ + @NonNull protected final WrapperPlayServerTitle CLEAR_TITLE_PACKET = new WrapperPlayServerTitle(), + /** + * Packet for resetting the title + */ + RESET_TITLE_PACKET = new WrapperPlayServerTitle(); + + static { + CLEAR_TITLE_PACKET.setAction(EnumWrappers.TitleAction.CLEAR); + RESET_TITLE_PACKET.setAction(EnumWrappers.TitleAction.RESET); + } + + /** + * Clears the title for the player. + * + * @param player player to whom to clear the title + * + * @apiNote clearing the title means that the player will be able to see it again if title-time is sent to him + */ + public void clearTitle(final @NonNull Player player) { + CLEAR_TITLE_PACKET.sendPacket(player); + } + + /** + * Clears the title for the players. + * + * @param players players to whom to clear the title + * + * @apiNote clearing the title means that the player will be able to see it again if title-time is sent to him + */ + public void clearTitle(final @NonNull Player... players) { + for (val player : players) CLEAR_TITLE_PACKET.sendPacket(player); + } + + /** + * Clears the title for the players. + * + * @param players players to whom to clear the title + * + * @apiNote clearing the title means that the player will be able to see it again if title-time is sent to him + */ + public void clearTitle(final @NonNull Iterable players) { + for (val player : players) CLEAR_TITLE_PACKET.sendPacket(player); + } + + /** + * Resets the title for the player. + * + * @param player player to whom to reset the title + * + * @apiNote resetting the title means that the player won't be able to see it again without resending the text + */ + public void resetTitle(final @NonNull Player player) { + RESET_TITLE_PACKET.sendPacket(player); + } + + /** + * Resets the title for the players. + * + * @param players players to whom to reset the title + * + * @apiNote resetting the title means that the player won't be able to see it again without resending the text + */ + public void resetTitle(final @NonNull Player... players) { + for (val player : players) RESET_TITLE_PACKET.sendPacket(player); + } + + /** + * Resets the title for the players. + * + * @param players players to whom to reset the title + * + * @apiNote resetting the title means that the player won't be able to see it again without resending the text + */ + public void resetTitle(final @NonNull Iterable players) { + for (val player : players) RESET_TITLE_PACKET.sendPacket(player); + } + + /** + * Sends the title to the player. + * + * @param title text of the title message + * @param player player to whom to send the title + * + * @apiNote title won't display until title-time is sent + */ + public void sendTitle(final @NonNull WrappedChatComponent title, + final @NonNull Player player) { + val packet = new WrapperPlayServerTitle(); + packet.setAction(EnumWrappers.TitleAction.TITLE); + packet.setTitle(title); + + packet.sendPacket(player); + } + + /** + * Sends the title to the player. + * + * @param title text of the title message + * @param players players to whom to send the title + * + * @apiNote title won't display until title-time is sent + */ + public void sendTitle(final @NonNull WrappedChatComponent title, + final @NonNull Player... players) { + val packet = new WrapperPlayServerTitle(); + packet.setAction(EnumWrappers.TitleAction.TITLE); + packet.setTitle(title); + + for (val player : players) packet.sendPacket(player); + } + + /** + * Sends the title to the player. + * + * @param title text of the title message + * @param players players to whom to send the title + * + * @apiNote title won't display until title-time is sent + */ + public void sendTitle(final @NonNull WrappedChatComponent title, + final @NonNull Iterable players) { + val packet = new WrapperPlayServerTitle(); + packet.setAction(EnumWrappers.TitleAction.TITLE); + packet.setTitle(title); + + for (val player : players) packet.sendPacket(player); + } + + /** + * Sends the subtitle to the player. + * + * @param title text of the subtitle message + * @param player player to whom to send the subtitle + * + * @apiNote subtitle won't display until title-time is sent + */ + public void sendSubtitle(final @NonNull WrappedChatComponent title, + final @NonNull Player player) { + val packet = new WrapperPlayServerTitle(); + packet.setAction(EnumWrappers.TitleAction.SUBTITLE); + packet.setTitle(title); + + packet.sendPacket(player); + } + + /** + * Sends the subtitle to the player. + * + * @param subtitle text of the subtitle message + * @param players players to whom to send the subtitle + * + * @apiNote subtitle won't display until title-time is sent + */ + public void sendSubtitle(final @NonNull WrappedChatComponent subtitle, + final @NonNull Player... players) { + val packet = new WrapperPlayServerTitle(); + packet.setAction(EnumWrappers.TitleAction.SUBTITLE); + packet.setTitle(subtitle); + + for (val player : players) packet.sendPacket(player); + } + + /** + * Sends the subtitle to the players. + * + * @param subtitle text of the subtitle message + * @param players players to whom to send the subtitle + * + * @apiNote subtitle won't display until title-time is sent + */ + public void sendSubtitle(final @NonNull WrappedChatComponent subtitle, + final @NonNull Iterable players) { + val packet = new WrapperPlayServerTitle(); + packet.setAction(EnumWrappers.TitleAction.SUBTITLE); + packet.setTitle(subtitle); + + for (val player : players) packet.sendPacket(player); + } + + /** + * Sends the title-time to the player. + * + * @param fadeIn time for the title to fade in + * @param stay time for the title to stay + * @param fadeOut time for the title to fade in + * @param player player to whom to send the subtitle + * + * @apiNote sending time is required to make the player see title and subtitle + * @apiNote sending time reverts the effect of clearing the title + */ + public void sendTitleTime(final int fadeIn, final int stay, final int fadeOut, + final @NonNull Player player) { + val packet = new WrapperPlayServerTitle(); + packet.setAction(EnumWrappers.TitleAction.TIMES); + packet.setFadeIn(fadeIn); + packet.setStay(stay); + packet.setFadeOut(fadeOut); + + packet.sendPacket(player); + } + + /** + * Sends the title-time to the players. + * + * @param fadeIn time for the title to fade in + * @param stay time for the title to stay + * @param fadeOut time for the title to fade in + * @param players players to whom to send the subtitle + * + * @apiNote sending time is required to make the player see title and subtitle + * @apiNote sending time reverts the effect of clearing the title + */ + public void sendTitleTime(final int fadeIn, final int stay, final int fadeOut, + final @NonNull Player... players) { + val packet = new WrapperPlayServerTitle(); + packet.setAction(EnumWrappers.TitleAction.TIMES); + packet.setFadeIn(fadeIn); + packet.setStay(stay); + packet.setFadeOut(fadeOut); + + for (val player : players) packet.sendPacket(player); + } + + /** + * Sends the title-time to the players. + * + * @param fadeIn time for the title to fade in + * @param stay time for the title to stay + * @param fadeOut time for the title to fade in + * @param players players to whom to send the subtitle + * + * @apiNote sending time is required to make the player see title and subtitle + * @apiNote sending time reverts the effect of clearing the title + */ + public void sendTitleTime(final int fadeIn, final int stay, final int fadeOut, + final @NonNull Iterable players) { + val packet = new WrapperPlayServerTitle(); + packet.setAction(EnumWrappers.TitleAction.TIMES); + packet.setFadeIn(fadeIn); + packet.setStay(stay); + packet.setFadeOut(fadeOut); + + for (val player : players) packet.sendPacket(player); + } +} diff --git a/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkUtilTest.java b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkUtilTest.java new file mode 100644 index 000000000..03f077106 --- /dev/null +++ b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/chunk/ChunkUtilTest.java @@ -0,0 +1,335 @@ +package ru.progrm_jarvis.minecraft.commons.chunk; + +import lombok.val; +import lombok.var; +import org.apache.commons.lang.math.RandomUtils; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.ArgumentMatchers; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import static ru.progrm_jarvis.minecraft.commons.chunk.ChunkUtil.*; + +class ChunkUtilTest { + + @Test + void testRangeCheckChunkLocalX() { + for (var chunkLocalX = -32; chunkLocalX <= 32; chunkLocalX++) { + final Executable executable; + { + val x = chunkLocalX; + executable = () -> ChunkUtil.rangeCheckChunkLocalX(x); + } + + if (chunkLocalX < 0 || chunkLocalX > 15) assertThrows(IllegalArgumentException.class, executable); + else assertDoesNotThrow(executable); + } + } + + @Test + void testRangeCheckChunkLocalY() { + for (var chunkLocalZ = -32; chunkLocalZ <= 32; chunkLocalZ++) { + final Executable executable; + { + val z = chunkLocalZ; + executable = () -> ChunkUtil.rangeCheckChunkLocalZ(z); + } + + if (chunkLocalZ < 0 || chunkLocalZ > 15) assertThrows(IllegalArgumentException.class, executable); + else assertDoesNotThrow(executable); + } + } + + @Test + void testRangeCheckChunkLocalZ() { + for (var chunkLocalY = -512; chunkLocalY <= 512; chunkLocalY++) { + final Executable executable; + { + val y = chunkLocalY; + executable = () -> ChunkUtil.rangeCheckChunkLocalY(y); + } + + if (chunkLocalY < 0 || chunkLocalY > 255) assertThrows(IllegalArgumentException.class, executable); + else assertDoesNotThrow(executable); + } + } + + @Test + void testRangeCheckChunkLocal() { + for (var chunkLocalY = -256; chunkLocalY <= 256; chunkLocalY++) { + val y = chunkLocalY; + for (var chunkLocalX = -32; chunkLocalX <= 32; chunkLocalX++) { + val x = chunkLocalX; + for (var chunkLocalZ = -32; chunkLocalZ <= 32; chunkLocalZ++) { + final Executable executable; + { + final int z = chunkLocalZ; + executable = () -> ChunkUtil.rangeCheckChunkLocal(x, y, z); + } + + if (chunkLocalX < 0 || chunkLocalX > 15 + || chunkLocalZ < 0 || chunkLocalZ > 15 + || chunkLocalY < 0 || chunkLocalY > 255) assertThrows( + IllegalArgumentException.class, executable + ); + else assertDoesNotThrow(executable); + } + } + } + } + + @Test + void testToChunkLong() { + byte signs = 0; + for (var i = 0; i < 128 + RandomUtils.nextInt(128) ; i++) { + final int x, z; + // make sure all sign combinations happen + switch (signs++ % 4) { + case 0: { + x = RandomUtils.nextInt(); + z = RandomUtils.nextInt(); + + break; + } + case 1: { + x = -RandomUtils.nextInt(); + z = RandomUtils.nextInt(); + + break; + } + case 2: { + x = RandomUtils.nextInt(); + z = -RandomUtils.nextInt(); + + break; + } + case 3: /* math doesn't work here :) */ default: { + x = -RandomUtils.nextInt(); + z = -RandomUtils.nextInt(); + + break; + } + } + + val chunk = toChunkLong(x, z); + assertEquals(x, chunkX(chunk)); + assertEquals(z, chunkZ(chunk)); + } + } + + @Test + void testChunkByLocation() { + long chunk; + + chunk = chunkAt(0, 0); + assertEquals(0, chunkX(chunk)); + assertEquals(0, chunkZ(chunk)); + + chunk = chunkAt(15, 15); + assertEquals(0, chunkX(chunk)); + assertEquals(0, chunkZ(chunk)); + + chunk = chunkAt(16, 16); + assertEquals(1, chunkX(chunk)); + assertEquals(1, chunkZ(chunk)); + + chunk = chunkAt(31, 31); + assertEquals(1, chunkX(chunk)); + assertEquals(1, chunkZ(chunk)); + + chunk = chunkAt(32, 32); + assertEquals(2, chunkX(chunk)); + assertEquals(2, chunkZ(chunk)); + + chunk = chunkAt(47, 47); + assertEquals(2, chunkX(chunk)); + assertEquals(2, chunkZ(chunk)); + + chunk = chunkAt(-1, -1); + assertEquals(-1, chunkX(chunk)); + assertEquals(-1, chunkZ(chunk)); + + chunk = chunkAt(-16, -16); + assertEquals(-1, chunkX(chunk)); + assertEquals(-1, chunkZ(chunk)); + + chunk = chunkAt(-17, -17); + assertEquals(-2, chunkX(chunk)); + assertEquals(-2, chunkZ(chunk)); + + chunk = chunkAt(-32, -32); + assertEquals(-2, chunkX(chunk)); + assertEquals(-2, chunkZ(chunk)); + + chunk = chunkAt(-33, -33); + assertEquals(-3, chunkX(chunk)); + assertEquals(-3, chunkZ(chunk)); + + chunk = chunkAt(-48, -48); + assertEquals(-3, chunkX(chunk)); + assertEquals(-3, chunkZ(chunk)); + + chunk = chunkAt(0, 15); + assertEquals(0, chunkX(chunk)); + assertEquals(0, chunkZ(chunk)); + + chunk = chunkAt(-1, 15); + assertEquals(-1, chunkX(chunk)); + assertEquals(0, chunkZ(chunk)); + + chunk = chunkAt(0, 15); + assertEquals(0, chunkX(chunk)); + assertEquals(0, chunkZ(chunk)); + + chunk = chunkAt(-31, 31); + assertEquals(-2, chunkX(chunk)); + assertEquals(1, chunkZ(chunk)); + + chunk = chunkAt(64, -32); + assertEquals(4, chunkX(chunk)); + assertEquals(-2, chunkZ(chunk)); + + chunk = chunkAt(10, 20); + assertEquals(0, chunkX(chunk)); + assertEquals(1, chunkZ(chunk)); + + chunk = chunkAt(-10, 20); + assertEquals(-1, chunkX(chunk)); + assertEquals(1, chunkZ(chunk)); + + chunk = chunkAt(10, -20); + assertEquals(0, chunkX(chunk)); + assertEquals(-2, chunkZ(chunk)); + + chunk = chunkAt(-10, -20); + assertEquals(-1, chunkX(chunk)); + assertEquals(-2, chunkZ(chunk)); + } + + @Test + void testGetChunkFromWorld() { + val world = mock(World.class); + + getChunk(world, toChunkLong(0, 0)); + verify(world).getChunkAt(ArgumentMatchers.eq(0), ArgumentMatchers.eq(0)); + + getChunk(world, toChunkLong(1, 0)); + verify(world).getChunkAt(ArgumentMatchers.eq(1), ArgumentMatchers.eq(0)); + + getChunk(world, toChunkLong(0, 1)); + verify(world).getChunkAt(ArgumentMatchers.eq(0), ArgumentMatchers.eq(1)); + + getChunk(world, toChunkLong(-1, 0)); + verify(world).getChunkAt(ArgumentMatchers.eq(-1), ArgumentMatchers.eq(0)); + + getChunk(world, toChunkLong(0, -1)); + verify(world).getChunkAt(ArgumentMatchers.eq(0), ArgumentMatchers.eq(-1)); + + getChunk(world, toChunkLong(25, 10)); + verify(world).getChunkAt(ArgumentMatchers.eq(25), ArgumentMatchers.eq(10)); + + getChunk(world, toChunkLong(-14, 8)); + verify(world).getChunkAt(ArgumentMatchers.eq(-14), ArgumentMatchers.eq(8)); + + getChunk(world, toChunkLong(23, -9)); + verify(world).getChunkAt(ArgumentMatchers.eq(23), ArgumentMatchers.eq(-9)); + + getChunk(world, toChunkLong(-6, -22)); + verify(world).getChunkAt(ArgumentMatchers.eq(-6), ArgumentMatchers.eq(-22)); + } + + @Test + void testToChunkLocalLocation() { + assertDoesNotThrow(() -> toChunkLocalLocationShort(0, 10, 10)); + assertDoesNotThrow(() -> toChunkLocalLocationShort(15, 10, 10)); + assertThrows(IllegalArgumentException.class, () -> toChunkLocalLocationShort(-1, 10, 10)); + assertThrows(IllegalArgumentException.class, () -> toChunkLocalLocationShort(16, 10, 10)); + + assertDoesNotThrow(() -> toChunkLocalLocationShort(10, 0, 10)); + assertDoesNotThrow(() -> toChunkLocalLocationShort(10, 255, 10)); + assertThrows(IllegalArgumentException.class, () -> toChunkLocalLocationShort(10, -1, 10)); + assertThrows(IllegalArgumentException.class, () -> toChunkLocalLocationShort(10, 256, 10)); + + assertDoesNotThrow(() -> toChunkLocalLocationShort(10, 10, 0)); + assertDoesNotThrow(() -> toChunkLocalLocationShort(10, 10, 15)); + assertThrows(IllegalArgumentException.class, () -> toChunkLocalLocationShort(10, 10, -1)); + assertThrows(IllegalArgumentException.class, () -> toChunkLocalLocationShort(10, 10, 16)); + + for (var y = 0; y < 255; y++) for (var x = 0; x < 15; x++) for (var z = 0; z < 15; z++) { + val chunkLocalLocation = toChunkLocalLocationShort(x, y, z); + + assertEquals(x, chunkLocalLocationX(chunkLocalLocation)); + assertEquals(y, chunkLocalLocationY(chunkLocalLocation)); + assertEquals(z, chunkLocalLocationZ(chunkLocalLocation)); + } + } + + @Test + void testChunkLocalLocationFromLocation() { + val world = mock(World.class); + + byte signs = 0; + for (var i = 0; i < 128 + RandomUtils.nextInt(129); i++) { + final int x, z, + y = RandomUtils.nextInt(256), // y is the same for location and chunk-local location + chunkLocalX = RandomUtils.nextInt(16), + chunkLocalZ = RandomUtils.nextInt(16); + // coordinates are generating by adding random amount of chunks to chunk-local coordinates; + // make sure all sign combinations happen + switch (signs++ % 4) { + case 0: { + x = chunkLocalX + 16 * RandomUtils.nextInt(); + z = chunkLocalZ + 16 * RandomUtils.nextInt(); + + break; + } + case 1: { + x = chunkLocalX + 16 * -RandomUtils.nextInt(); + z = chunkLocalZ + 16 * RandomUtils.nextInt(); + + break; + } + case 2: { + x = chunkLocalX + 16 * RandomUtils.nextInt(); + z = chunkLocalZ + 16 * -RandomUtils.nextInt(); + + break; + } + case 3: /* math doesn't work here :) */ default: { + x = chunkLocalX + 16 * -RandomUtils.nextInt(); + z = chunkLocalZ + 16 * -RandomUtils.nextInt(); + + break; + } + } + + val location = new Location(world, x, y, z); + val chunkLocalLocation = ChunkUtil.chunkLocalLocation(location); + + assertEquals(chunkLocalX, chunkLocalLocationX(chunkLocalLocation)); + assertEquals(y, chunkLocalLocationY(chunkLocalLocation)); + assertEquals(chunkLocalZ, chunkLocalLocationZ(chunkLocalLocation)); + } + } + + @Test + void testGetChunkBlock() { + ////////////////////////////////////////////////////////////////////////////////////////////// + // Note: verify(..).foo() requires some time so full coverage of XYZ requires too much time // + // because of this axises are stepped by random value // + ////////////////////////////////////////////////////////////////////////////////////////////// + + int x = 0, y = 0, z = 0; + while ((x += RandomUtils.nextInt(1)) < 16 + && (z += RandomUtils.nextInt(1)) < 16 + && (y += RandomUtils.nextInt(16)) < 255) { + val chunk = mock(Chunk.class); // mocked here in case all 3 random increments are 0 + getChunkBlock(chunk, ChunkUtil.toChunkLocalLocationShort(x, y, z)); + verify(chunk).getBlock(x, y, z); + } + } +} \ No newline at end of file diff --git a/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImageTest.java b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImageTest.java new file mode 100644 index 000000000..d2ebc197a --- /dev/null +++ b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/mapimage/MapImageTest.java @@ -0,0 +1,69 @@ +package ru.progrm_jarvis.minecraft.commons.mapimage; + +import lombok.val; +import lombok.var; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import ru.progrm_jarvis.minecraft.commons.mapimage.MapImage.Delta; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.*; + +class MapImageTest { + + /////////////////////////////////////////////////////////////////////////// + // Delta + /////////////////////////////////////////////////////////////////////////// + + @Test + void testDeltaOf() { + assertTrue(Delta.EMPTY.isEmpty()); + + assertSame(Delta.EMPTY, Delta.of(new byte[0], 0, 0, 0)); + assertSame(Delta.EMPTY, Delta.of(new byte[0], 0, 0, 135)); + assertSame(Delta.EMPTY, Delta.of(new byte[0], 0, 1213, 0)); + assertSame(Delta.EMPTY, Delta.of(new byte[0], 0, 125, 65)); + assertSame(Delta.EMPTY, Delta.of(new byte[0], 0, -12, 24)); + assertSame(Delta.EMPTY, Delta.of(new byte[0], 0, 19, -32)); + assertSame(Delta.EMPTY, Delta.of(new byte[0], 0, 12, -24)); + + assertEquals(new Delta.SinglePixel((byte) 1, 10, 10), Delta.of(new byte[]{1}, 1, 10, 10)); + assertEquals(new Delta.SinglePixel((byte) 32, 100, 10), Delta.of(new byte[]{32}, 1, 100, 10)); + assertEquals(new Delta.SinglePixel((byte) 12, 100, 92), Delta.of(new byte[]{12}, 1, 100, 92)); + assertEquals(new Delta.SinglePixel((byte) -127, 13, 12), Delta.of(new byte[]{-127}, 1, 13, 12)); + assertEquals(new Delta.SinglePixel((byte) 127, 28, 10), Delta.of(new byte[]{127}, 1, 28, 10)); + assertEquals(new Delta.SinglePixel((byte) -128, 23, 23), Delta.of(new byte[]{-128}, 1, 23, 23)); + assertEquals(new Delta.SinglePixel((byte) -23, 25, 122), Delta.of(new byte[]{-23}, 1, 25, 122)); + + assertEquals(new Delta.SinglePixel((byte) 1, 10, 10), Delta.of(new byte[]{1}, 1, 10, 10)); + assertEquals(new Delta.SinglePixel((byte) 32, 100, 10), Delta.of(new byte[]{32}, 1, 100, 10)); + assertEquals(new Delta.SinglePixel((byte) 12, 100, 92), Delta.of(new byte[]{12}, 1, 100, 92)); + assertEquals(new Delta.SinglePixel((byte) -127, 13, 12), Delta.of(new byte[]{-127}, 1, 13, 12)); + assertEquals(new Delta.SinglePixel((byte) 127, 28, 10), Delta.of(new byte[]{127}, 1, 28, 10)); + assertEquals(new Delta.SinglePixel((byte) -128, 23, 23), Delta.of(new byte[]{-128}, 1, 23, 23)); + assertEquals(new Delta.SinglePixel((byte) -23, 25, 12), Delta.of(new byte[]{-23}, 1, 25, 12)); + + val random = new Random(); + + for (var i = 0; i < 128 + random.nextInt(128); i++) { + final int + width = 2 + random.nextInt(MapImage.WIDTH - 1), // [2 ; WIDTH] + height = 2 + random.nextInt(MapImage.HEIGHT - 1), // [2 ; HEIGHT] + leastX = random.nextInt(MapImage.WIDTH - width + 1), // [0 ; WIDTH - xLength] + leastY = random.nextInt(MapImage.HEIGHT - height + 1); // [0 ; HEIGHT - yLength] + + val pixels = new byte[width * height]; + // populate pixels with random values + random.nextBytes(pixels); + + val pixelsCopy = pixels.clone(); + + MatcherAssert.assertThat( + new Delta.NonEmpty(pixelsCopy, width, leastX, leastY), + Matchers.is(Delta.of(pixels, width, leastX, leastY)) + ); + } + } +} \ No newline at end of file diff --git a/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/mojang/MojangApiManagerTest.java b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/mojang/MojangApiManagerTest.java new file mode 100644 index 000000000..7e0530e01 --- /dev/null +++ b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/mojang/MojangApiManagerTest.java @@ -0,0 +1,84 @@ +package ru.progrm_jarvis.minecraft.commons.mojang; + +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import lombok.val; +import org.apache.http.client.methods.HttpGet; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import ru.progrm_jarvis.mcunit.io.http.HttpClientMocks; + +import java.util.Base64; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static ru.progrm_jarvis.minecraft.commons.mojang.MojangApiManager.USERNAME_TO_UUID_AT_TIME_URI_PREFIX; +import static ru.progrm_jarvis.minecraft.commons.mojang.MojangApiManager.UUID_TO_PROFILE_URI_PREFIX; + +class MojangApiManagerTest { + + private static final UUID + UUID_1 = UUID.fromString("29be10b1-b2d1-4130-b8f2-4fd14d3ccb62"); + private static final String + MOJANG_UUID_1 = "29be10b1b2d14130b8f24fd14d3ccb62", + USERNAME_1 = "PROgrm_JARvis", + TEXTURES_PROPERTY_NAME = "textures", + PROFILE_PROPERTY_1_VALUE_1 = "eyJ0aW1lc3RhbXAiOjE1NDU0MDcyMDUwNjksInByb2ZpbGVJZCI6IjI5YmUxMGIxYjJkMTQxMzBiO" + + "GYyNGZkMTRkM2NjYjYyIiwicHJvZmlsZU5hbWUiOiJQUk9ncm1fSkFSdmlzIiwic2lnbmF0dXJlUmVxdWlyZWQiOnRydWUsI" + + "nRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9lYTU0MTdhM" + + "zBlZDU3OTQ3YjFlZDliMmUzMWNkZWJlZDJhYzE5YzlhODkyZGU2NDY4NjNhMTA4YTNkOTE0MzE5In19fQ==", + PROFILE_PROPERTY_1_SIGNATURE_1 = "P3QNNsvMIx9lNpz9Is1gc9eeUlz7zj0KWLBIgwhswschofE3X1NjQVoykKy1SAA8Ru6SmXmPs" + + "W1VtWdLzjCdtsCR0h/1mLTpruu5VFuBB6hwILHCeKabLIdSRzKKLXUHNNSd6kLe+Qis/QSOlGESQT8NYuxxSp2NlT0/YD6ot" + + "8pd/KxXyVRJrU/VPYfYJnxv9KeN4PFb67F82dZ33dlZsIvRN2LlFPluQeQ4xrdSojVyUVTefTQ5HGQBibIFv0dcbKScPHqYA" + + "4qKT61oMmsJEvzZ94HQKANTgEAO8D1KEwt+2Q3mzvr/lCqQq3MzmWQRympCbP3XVYe06hx2kk4awNkZ9OT+o9mOGOR7T7Rsz" + + "E4I0ghIg2wgo30cGK+YSjRGpvoX3BJafATzyuGssN22y1hmUMqTzK+0jkQpTYxK+9pxslQ/LIPp9zTKJyd5HwE25pyFFPOj+" + + "fzYWQmb0Vf92SVDWeE2c6oE0NFyDmyRLu40m1FRWn0AW+6v0G/38Zbc6QESgT6YOrUvUjTgqqbC95rSLjRCPMj6bE6QXPDqH" + + "yJQnGrDsws+QO9BafajpQatd8njHFASYIgs675NJy9dnEBjw1FLzpED5hIS0dLRUeEzAhWL9j+89tTPflysTrJxu5HR41Ydd" + + "dd7CQRmVHOFO59HjSpHXo/Xp47uJPKcIyA="; + private static final GameProfile PROFILE_1 = new GameProfile(UUID_1, USERNAME_1); + + static { + PROFILE_1.getProperties().put( + TEXTURES_PROPERTY_NAME, + new Property(TEXTURES_PROPERTY_NAME, PROFILE_PROPERTY_1_VALUE_1, PROFILE_PROPERTY_1_SIGNATURE_1) + ); + } + + @BeforeAll + static void validateBase64s() { + val decoder = Base64.getDecoder(); + + assertNotNull(decoder.decode(PROFILE_PROPERTY_1_VALUE_1)); + assertNotNull(decoder.decode(PROFILE_PROPERTY_1_SIGNATURE_1)); + } + + @Test + void testGetUuid() { + try (val mojangApiManager = new MojangApiManager(MojangApiManager.Configuration.builder() + .httpClient(manager -> HttpClientMocks.mockHttpClient() + .responding( + new HttpGet(USERNAME_TO_UUID_AT_TIME_URI_PREFIX + USERNAME_1), + "{\"id\":\"" + MOJANG_UUID_1 +"\",\"name\":\"" + USERNAME_1 + "\"}" + )) + .build())) { + assertEquals(UUID_1, mojangApiManager.getUuid(USERNAME_1)); + } + } + + @Test + void testGetProfile() { + try (val mojangApiManager = new MojangApiManager(MojangApiManager.Configuration.builder() + .httpClient(manager -> HttpClientMocks.mockHttpClient() + .responding( + new HttpGet(UUID_TO_PROFILE_URI_PREFIX + MOJANG_UUID_1 + "?unsigned=false"), + "{\"id\":\"" + MOJANG_UUID_1 + "\",\"name\":\"" + USERNAME_1 + "\",\"properties\":" + + "[{\"name\":\"textures\",\"value\":\"" + PROFILE_PROPERTY_1_VALUE_1 + "\"," + + "\"signature\":\"" + PROFILE_PROPERTY_1_VALUE_1 + "\"}]}" + )) + .build())) { + + assertEquals(PROFILE_1, mojangApiManager.getProfile(UUID_1, true)); + } + } +} \ No newline at end of file diff --git a/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/mojang/MojangUtilTest.java b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/mojang/MojangUtilTest.java new file mode 100644 index 000000000..34b437a5b --- /dev/null +++ b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/mojang/MojangUtilTest.java @@ -0,0 +1,26 @@ +package ru.progrm_jarvis.minecraft.commons.mojang; + +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MojangUtilTest { + + @Test + void testFromMojangUuid() { + assertEquals( + UUID.fromString("29be10b1-b2d1-4130-b8f2-4fd14d3ccb62"), + MojangUtil.fromMojangUuid("29be10b1b2d14130b8f24fd14d3ccb62") + ); + } + + @Test + void testToMojangUuid() { + assertEquals( + "29be10b1b2d14130b8f24fd14d3ccb62", + MojangUtil.toMojangUuid(UUID.fromString("29be10b1-b2d1-4130-b8f2-4fd14d3ccb62")) + ); + } +} \ No newline at end of file diff --git a/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/nms/PacketWrapperUtilTest.java b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/nms/PacketWrapperUtilTest.java new file mode 100644 index 000000000..f442bb4fc --- /dev/null +++ b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/nms/PacketWrapperUtilTest.java @@ -0,0 +1,73 @@ +package ru.progrm_jarvis.minecraft.commons.nms; + +import com.comphenix.packetwrapper.AbstractPacket; +import com.google.common.reflect.ClassPath; +import lombok.val; +import org.apache.commons.lang.math.RandomUtils; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import ru.progrm_jarvis.minecraft.commons.nms.protocol.misc.PacketWrapperUtil; +import ru.progrm_jarvis.reflector.wrapper.invoke.InvokeConstructorWrapper; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static com.google.common.reflect.ClassPath.from; +import static java.util.regex.Pattern.compile; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class PacketWrapperUtilTest { + + private static final Pattern + PACKET_WRAPPER_CLASS_NAME_PATTERN = compile("Wrapper[A-Z][a-z]+(?:Client|Server)[A-Z][A-Za-z]+"), + PACKET_WRAPPER_TO_STRING_PATTERN = compile("Wrapper[A-Z][a-z]+(?:Client|Server)[A-Z][A-Za-z]+\\{.*}"); + + @Test + void getterNameToStringTest() { + assertEquals("lel", PacketWrapperUtil.getterNameToString("lel")); + assertEquals("foo", PacketWrapperUtil.getterNameToString("getFoo")); + assertEquals("barBaz", PacketWrapperUtil.getterNameToString("getBarBaz")); + assertEquals("get", PacketWrapperUtil.getterNameToString("get")); + } + + @Test + @Disabled("Requires PortocolManager singleton at runtime") + @SuppressWarnings({"UnstableApiUsage", "unchecked"}) + void defaultPacketsTest() throws IOException { + val classLoader = AbstractPacket.class.getClassLoader(); + + from(classLoader) + .getTopLevelClasses(AbstractPacket.class.getPackage().getName()) + .stream() + .filter(classInfo -> PACKET_WRAPPER_CLASS_NAME_PATTERN.matcher(classInfo.getSimpleName()).matches()) + .map(ClassPath.ClassInfo::getName) + .map((Function>) className + -> { + try { + return (Class) classLoader.loadClass(className); + } catch (final ClassNotFoundException e) { + throw new IllegalStateException("Could not find class by name \"" + className + '"'); + } + }) + .map((Function, Constructor>) clazz -> { + try { + return clazz.getDeclaredConstructor(); + } catch (final NoSuchMethodException e) { + throw new IllegalStateException("Could not find empty constructor of class " + clazz); + } + }) + .map(InvokeConstructorWrapper::from) + .collect(Collectors.toSet()) + .forEach(constructor -> { + System.out.println("Testing: " + constructor); + for (int i = 0; i < 3 + RandomUtils.nextInt(3); i++) + assertDoesNotThrow( + () -> PacketWrapperUtil.toString(constructor.invoke()) + ); + }); + } +} \ No newline at end of file diff --git a/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/BitwiseUtilTest.java b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/BitwiseUtilTest.java new file mode 100644 index 000000000..975de58da --- /dev/null +++ b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/BitwiseUtilTest.java @@ -0,0 +1,14 @@ +package ru.progrm_jarvis.minecraft.commons.util; + +import lombok.var; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class BitwiseUtilTest { + + @Test + void testUnsignedIntByteConversions() { + for (var i = 0; i < 255; i++) assertEquals(i, BitwiseUtil.byteToUnsignedInt(BitwiseUtil.unsignedIntToByte(i))); + } +} \ No newline at end of file diff --git a/commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/LocationUtilTest.java b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/LocationUtilTest.java similarity index 100% rename from commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/LocationUtilTest.java rename to minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/LocationUtilTest.java diff --git a/commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/SystemPropertyUtilTest.java b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/SystemPropertyUtilTest.java similarity index 100% rename from commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/SystemPropertyUtilTest.java rename to minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/SystemPropertyUtilTest.java diff --git a/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/chat/ChatComponentUtilTest.java b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/chat/ChatComponentUtilTest.java new file mode 100644 index 000000000..4121dea1a --- /dev/null +++ b/minecraft-commons/src/test/java/ru/progrm_jarvis/minecraft/commons/util/chat/ChatComponentUtilTest.java @@ -0,0 +1,30 @@ +package ru.progrm_jarvis.minecraft.commons.util.chat; + +import com.comphenix.protocol.wrappers.WrappedChatComponent; +import com.google.gson.GsonBuilder; +import lombok.val; +import org.junit.jupiter.api.Test; +import ru.progrm_jarvis.mcunit.annotation.EnabledIfNms; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ChatComponentUtilTest { + + @Test + @EnabledIfNms + void testWrappedChatComponentGsonSerializer() { + val gson = new GsonBuilder() + .registerTypeAdapter(WrappedChatComponent.class, ChatComponentUtil.wrappedChatComponentGsonSerializer()) + .create(); + + assertEquals( + WrappedChatComponent.fromJson("{\"text\": \"Hello world!\"}"), + gson.fromJson("{\"text\": \"Hello world!\"}", WrappedChatComponent.class) + ); + + assertEquals( + WrappedChatComponent.fromText("Hello world"), + gson.fromJson("Hello world", WrappedChatComponent.class) + ); + } +} \ No newline at end of file diff --git a/nms/pom.xml b/nms/pom.xml deleted file mode 100644 index 1dfc69603..000000000 --- a/nms/pom.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - 4.0.0 - - minecraft-utils - ru.progrm-jarvis.minecraft - 0.1.0-SNAPSHOT - - nms-utils - - jar - - - - org.spigotmc - spigot-api - - - com.comphenix.protocol - ProtocolLib-API - - - com.comphenix.packetwrapper - PacketWrapper - - - ru.progrm-jarvis.reflector - reflector - - - - org.projectlombok - lombok - - - - - org.junit.jupiter - junit-jupiter-api - - - org.mockito - mockito-core - - - \ No newline at end of file diff --git a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/Conversions.java b/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/Conversions.java deleted file mode 100644 index 1d34d5f6b..000000000 --- a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/Conversions.java +++ /dev/null @@ -1,49 +0,0 @@ -package ru.progrm_jarvis.minecraft.nmsutils; - -import com.comphenix.protocol.reflect.EquivalentConverter; -import com.comphenix.protocol.wrappers.EnumWrappers; -import com.comphenix.protocol.wrappers.EnumWrappers.Direction; -import com.comphenix.protocol.wrappers.EnumWrappers.Particle; -import com.comphenix.protocol.wrappers.Vector3F; -import lombok.AccessLevel; -import lombok.NonNull; -import lombok.experimental.FieldDefaults; -import lombok.experimental.UtilityClass; - -/** - * Utilities for NSM {@literal <}{@literal >} API conversions - */ -@UtilityClass -@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal=true) -public class Conversions { - - // simple objects - EquivalentConverter VECTOR_3_F_EQUIVALENT_CONVERTER = Vector3F.getConverter(); - // enum wrappers - EquivalentConverter DIRECTION_EQUIVALENT_CONVERTER = EnumWrappers.getDirectionConverter(); - EquivalentConverter PARTICLE_EQUIVALENT_CONVERTER = EnumWrappers.getParticleConverter(); - - public Object toNms(@NonNull final Vector3F vector3F) { - return VECTOR_3_F_EQUIVALENT_CONVERTER.getGeneric(vector3F); - } - - public Vector3F toVector3F(@NonNull final Object nms) { - return VECTOR_3_F_EQUIVALENT_CONVERTER.getSpecific(nms); - } - - public Object toNms(@NonNull final Direction direction) { - return DIRECTION_EQUIVALENT_CONVERTER.getGeneric(direction); - } - - public Direction toDirection(@NonNull final Object nms) { - return DIRECTION_EQUIVALENT_CONVERTER.getSpecific(nms); - } - - public Object toNms(@NonNull final Particle particle) { - return PARTICLE_EQUIVALENT_CONVERTER.getGeneric(particle); - } - - public Particle toParticle(@NonNull final Object nms) { - return PARTICLE_EQUIVALENT_CONVERTER.getSpecific(nms); - } -} diff --git a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/NmsUtil.java b/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/NmsUtil.java deleted file mode 100644 index 26a355c4d..000000000 --- a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/NmsUtil.java +++ /dev/null @@ -1,152 +0,0 @@ -package ru.progrm_jarvis.minecraft.nmsutils; - -import com.comphenix.protocol.wrappers.WrappedDataWatcher; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.Value; -import lombok.experimental.UtilityClass; -import lombok.val; -import org.bukkit.Bukkit; -import ru.progrm_jarvis.minecraft.nmsutils.metadata.DataWatcherFactory; -import ru.progrm_jarvis.minecraft.nmsutils.metadata.LatestDataWatcherFactory; -import ru.progrm_jarvis.minecraft.nmsutils.metadata.LegacyDataWatcherFactory; -import ru.progrm_jarvis.reflector.wrapper.FieldWrapper; -import ru.progrm_jarvis.reflector.wrapper.fast.FastFieldWrapper; - -import static ru.progrm_jarvis.reflector.Reflector.classForName; -import static ru.progrm_jarvis.reflector.Reflector.getDeclaredField; - -/** - * Utility for NMS-related features - */ -@UtilityClass -public class NmsUtil { - - private final NmsVersion NMS_VERSION = NmsVersion.computeCurrent(); - /** - * Base package of NMS (net.minecraft.server.{version}) - */ - private final String NMS_PACKAGE = "net.minecraft.server." + NMS_VERSION.name, - /** - * Base package of CraftBukkit (org.bukkit.craftbukkit.{version}) - */ - CRAFT_BUKKIT_PACKAGE = "org.bukkit.craftbukkit." + NMS_VERSION.name; - - /** - * Field of {nms}.Entity class field responsible for entity int-UID generation. - */ - private final FieldWrapper ENTITY_COUNT_FIELD = FastFieldWrapper.from(getDeclaredField( - classForName(getNmsPackage().concat(".Entity")), "entityCount" - )); - - /** - * DataWatcher factory valid for current server version - */ - private final DataWatcherFactory DATA_WATCHER_FACTORY = NMS_VERSION.generation < 9 - ? new LegacyDataWatcherFactory() : new LatestDataWatcherFactory(); - - /** - * Gets version of the current server. - * - * @return version of this server - */ - public NmsVersion getVersion() { - return NMS_VERSION; - } - - /** - * Gets base package of NMS (net.minecraft.server.{version}) - * - * @return base package of NMS - */ - public String getNmsPackage() { - return NMS_PACKAGE; - } - - /** - * Gets base package of CraftBukkit (org.bukkit.craftbukkit.{version}) - * - * @return base package of CraftBukkit - */ - public String getCraftBukkitPackage() { - return CRAFT_BUKKIT_PACKAGE; - } - - /** - * Gets DataWatcher factory valid for current server version. - * - * @return DataWatcher factory valid for current server version - */ - public static DataWatcherFactory getDataWatcherFactory() { - return DATA_WATCHER_FACTORY; - } - - /** - * Creates new DataWatcher modifier valid for current server version. - * - * @return DataWatcher modifier valid for current server version - */ - public DataWatcherFactory.DataWatcherModifier dataWatcherModifier() { - return DATA_WATCHER_FACTORY.modifier(); - } - - /** - * Creates new DataWatcher modifier from DataWatcher specified which valid for current server version. - * - * @param dataWatcher DataWatcher from which to create DataWatcher modifier - * @return DataWatcher modifier valid for current server version - */ - public DataWatcherFactory.DataWatcherModifier dataWatcherModifier(@NonNull final WrappedDataWatcher dataWatcher) { - return DATA_WATCHER_FACTORY.modifier(dataWatcher); - } - - /** - * Gets id for entity preventing from conflicts with real entities. - * - * @return new id for an entity - */ - public int nextEntityId() { - synchronized (ENTITY_COUNT_FIELD) { - return ENTITY_COUNT_FIELD.getAndCompute(id -> id + 1); - } - } - - /** - * Version of a server. - */ - @Value - @RequiredArgsConstructor - public static final class NmsVersion { - - /** - * Name of the version - */ - @NonNull private String name; - - /** - * Generation of a version (such as 13 for minecraft 1.13.2) - */ - private short generation; - - /** - * Constructs a new NMS version by name specified (such as v1_12_R1). - * - * @param name name of a version - */ - private NmsVersion(@NonNull final String name) { - this(name, Short.parseShort(name.substring(3, name.indexOf('_', 4)))); - } - - /** - * Computes current version of NMS for this server - * based on implementation of {@link org.bukkit.Server} accessible via {@link Bukkit#getServer()}. - * - * @return current NMS version - */ - public static NmsVersion computeCurrent() { - val craftServerPackage = Bukkit.getServer().getClass().getPackage().getName(); - - return new NmsVersion(craftServerPackage.substring(craftServerPackage.lastIndexOf('.') + 1)); - } - } -} diff --git a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/ToStringUtil.java b/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/ToStringUtil.java deleted file mode 100644 index 8442fbec5..000000000 --- a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/ToStringUtil.java +++ /dev/null @@ -1,104 +0,0 @@ -package ru.progrm_jarvis.minecraft.nmsutils; - -import com.comphenix.packetwrapper.PacketWrapper; -import com.google.common.base.Function; -import com.google.common.base.MoreObjects; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import lombok.AccessLevel; -import lombok.NonNull; -import lombok.SneakyThrows; -import lombok.experimental.FieldDefaults; -import lombok.experimental.UtilityClass; -import lombok.val; -import ru.progrm_jarvis.reflector.wrapper.FieldWrapper; -import ru.progrm_jarvis.reflector.wrapper.MethodWrapper; -import ru.progrm_jarvis.reflector.wrapper.fast.FastMethodWrapper; - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Map; -import java.util.stream.Collectors; - -@UtilityClass -@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public class ToStringUtil { - - String FIELDS_CACHE_CONCURRENCY_LEVEL_PROPERTY_NAME - = ToStringUtil.class.getTypeName().concat(".FIELDS_CACHE_CONCURRENCY_LEVEL"); - - int FIELDS_CACHE_CONCURRENCY_LEVEL = Integer.parseInt(MoreObjects.firstNonNull( - System.getProperty(FIELDS_CACHE_CONCURRENCY_LEVEL_PROPERTY_NAME), "2") - ); - - private Cache, Map>> FIELDS_CACHE = CacheBuilder.newBuilder() - .concurrencyLevel(FIELDS_CACHE_CONCURRENCY_LEVEL) - .build(); - - String METHODS_CACHE_CONCURRENCY_LEVEL_PROPERTY_NAME - = ToStringUtil.class.getTypeName().concat(".METHODS_CACHE_CONCURRENCY_LEVEL"); - - int METHODS_CACHE_CONCURRENCY_LEVEL = Integer.parseInt(MoreObjects.firstNonNull( - System.getProperty(METHODS_CACHE_CONCURRENCY_LEVEL_PROPERTY_NAME), "2") - ); - - private Cache, Map>> METHODS_CACHE = CacheBuilder.newBuilder() - .concurrencyLevel(METHODS_CACHE_CONCURRENCY_LEVEL) - .build(); - - @SneakyThrows - public String toString(final PacketWrapper packetWrapper) { - if (packetWrapper == null) return "null"; - - final Map> methods; - final String className; - { - val clazz = packetWrapper.getClass(); - methods = METHODS_CACHE.get(clazz, () -> Arrays.stream(clazz.getDeclaredMethods()) - .filter(method -> method.getName().startsWith("get")) - .collect(Collectors.toMap( - method -> getterNameToString(method.getName()), - (Function>) FastMethodWrapper::from - ))); - className = clazz.getName(); - } - - - if (methods.isEmpty()) return className + "{}"; - - val stringBuilder = new StringBuilder(className) - .append('{'); - - { - val entries = methods.entrySet().iterator(); - boolean hasNext = entries.hasNext(); - while (hasNext) { - val entry = entries.next(); - // invoked before append() to exit if an exception occurs without any unneeded operations - val value = entry.getValue().invoke(packetWrapper); - - stringBuilder - .append(entry.getKey()) - .append(String.valueOf(value)); - - hasNext = entries.hasNext(); - if (hasNext) stringBuilder - .append(',') - .append(' '); - } - } - - return stringBuilder - .append('}') - .toString(); - } - - public String getterNameToString(@NonNull final String getterName) { - if (getterName.startsWith("get")) { - val name = getterName.substring(3); - if (name.length() == 0) return "get"; - else return name.substring(0, 1).toLowerCase() + name.substring(1); - } else return getterName; - } - -} diff --git a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/metadata/DataWatcherFactory.java b/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/metadata/DataWatcherFactory.java deleted file mode 100644 index c500a6ea3..000000000 --- a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/metadata/DataWatcherFactory.java +++ /dev/null @@ -1,416 +0,0 @@ -package ru.progrm_jarvis.minecraft.nmsutils.metadata; - -import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.wrappers.*; -import com.comphenix.protocol.wrappers.EnumWrappers.Direction; -import org.bukkit.inventory.ItemStack; -import ru.progrm_jarvis.minecraft.nmsutils.Conversions; - -import java.util.Optional; -import java.util.UUID; - -@SuppressWarnings("OptionalUsedAsFieldOrParameterType") -public interface DataWatcherFactory { - - /** - * Creates watchable object for {@link Byte} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchable(int id, Byte value); - - /** - * Creates watchable object for {@link Integer} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchable(int id, Integer value); - - /** - * Creates watchable object for {@link Float} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchable(int id, Float value); - - /** - * Creates watchable object for {@link String} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchable(int id, String value); - - /** - * Creates watchable object for {@code IChatBaseComponent} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchableIChatBaseComponent(int id, Object value); - - /** - * Creates watchable object for {@link WrappedChatComponent} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - default WrappedWatchableObject createWatchable(final int id, final WrappedChatComponent value) { - return createWatchableIChatBaseComponent(id, value.getHandle()); - } - - /** - * Creates watchable object for NMS {@code ItemStack} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchableItemStack(int id, Object value); - - /** - * Creates watchable object for {@link ItemStack} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - default WrappedWatchableObject createWatchable(final int id, final ItemStack value) { - return createWatchableItemStack(id, MinecraftReflection.getMinecraftItemStack(value)); - } - - /** - * Creates watchable object for {@code Optional} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchableOptionalIBlockData(int id, Optional value); - - /** - * Creates watchable object for {@link Boolean} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchable(int id, Boolean value); - - /** - * Creates watchable object for {@code Vector3f} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchableVector3f(int id, Object value); - /** - * Creates watchable object for {@link Vector3F} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - default WrappedWatchableObject createWatchable(final int id, final Vector3F value) { - return createWatchableVector3f(id, Conversions.toNms(value)); - } - - /** - * Creates watchable object for {@link BlockPosition} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchable(int id, BlockPosition value); - - /** - * Creates watchable object for {@link Optional} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchableOptionalBlockPosition(int id, Optional value); - - /** - * Creates watchable object for {@code EnumDirection} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchableEnumDirection(int id, Object value); - - /** - * Creates watchable object for {@link Direction} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - default WrappedWatchableObject createWatchable(final int id, final Direction value) { - return createWatchableEnumDirection(id, Conversions.toNms(value)); - } - - /** - * Creates watchable object for {@link Optional} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchableOptionalUUID(int id, Optional value); - - /** - * Creates watchable object for {@code NBTTagCompound} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchableNBTTagCompound(int id, Object value); - - /** - * Creates watchable object for {@link Object} value at index specified. - * - * @param id id of a value - * @param value value - * @return created watchable object - */ - WrappedWatchableObject createWatchableObject(int id, Object value); - - /** - * Creates new modifier for {@link WrappedDataWatcher} specified. - * - * @param watcher which to use as modifier backend - * @return created modifier - */ - DataWatcherModifier modifier(WrappedDataWatcher watcher); - - /** - * Creates new modifier of {@link WrappedDataWatcher}. - * - * @return created modifier - */ - DataWatcherModifier modifier(); - - /** - * Modifier of {@link WrappedDataWatcher} which applies all changes to DataWatcher. - */ - interface DataWatcherModifier { - - /** - * Deeply clones this modifier to a new one. - * - * @return this modifier's copy - */ - DataWatcherModifier clone(); - - /** - * Sets DataWatcher's modifier to specified {@link Byte} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier set(int id, Byte value); - - /** - * Sets DataWatcher's modifier to specified {@link Integer} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier set(int id, Integer value); - - /** - * Sets DataWatcher's modifier to specified {@link Float} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier set(int id, Float value); - - /** - * Sets DataWatcher's modifier to specified {@link String} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier set(int id, String value); - - /** - * Sets DataWatcher's modifier to specified {@code IChatBaseComponent} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier setIChatBaseComponent(int id, Object value); - - /** - * Sets DataWatcher's modifier to specified {@link WrappedChatComponent} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - default DataWatcherModifier setIChatBaseComponent(final int id, final WrappedChatComponent value) { - return setIChatBaseComponent(id, value.getHandle()); - } - - /** - * Sets DataWatcher's modifier to specified NMS {@code ItemStack} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier setItemStack(int id, Object value); - - /** - * Sets DataWatcher's modifier to specified {@link ItemStack} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - default DataWatcherModifier set(final int id, final ItemStack value) { - return setItemStack(id, MinecraftReflection.getMinecraftItemStack(value)); - } - - /** - * Sets DataWatcher's modifier to specified {@code Optional} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier setOptionalIBlockData(int id, Optional value); - - /** - * Sets DataWatcher's modifier to specified {@link Boolean} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier set(int id, Boolean value); - - /** - * Sets DataWatcher's modifier to specified {@code Vector3f} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier setVector3f(int id, Object value); - - /** - * Sets DataWatcher's modifier to specified {@link Vector3F} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - default DataWatcherModifier set(final int id, final Vector3F value) { - return setVector3f(id, Conversions.toNms(value)); - } - - /** - * Sets DataWatcher's modifier to specified {@link BlockPosition} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier set(int id, BlockPosition value); - - /** - * Sets DataWatcher's modifier to specified {@link Optional} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier setOptionalBlockPosition(int id, Optional value); - - /** - * Sets DataWatcher's modifier to specified {@code EnumDirection} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier setEnumDirection(int id, Object value); - - /** - * Sets DataWatcher's modifier to specified {@link Direction} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - default DataWatcherModifier set(final int id, final Direction value) { - return setEnumDirection(id, Conversions.toNms(value)); - } - - /** - * Sets DataWatcher's modifier to specified {@code NBTTagCompound} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier setNBTTagCompound(int id, Object value); - - /** - * Sets DataWatcher's modifier to specified {@link Optional} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - DataWatcherModifier setOptionalUUID(int id, Optional value); - - /** - * Sets DataWatcher's modifier to specified {@link Object} value at specified index. - * - * @param id id of a value - * @param value value to set at id - * @return this DataWatcher builder - */ - // should be used if and only if none of default #setOptionalBlockPosition(id, value) methods don't provide type given - DataWatcherModifier setObject(int id, Object value); - - /** - * Returns DataWatcher modified. - * - * @return DataWatcher modified - */ - WrappedDataWatcher dataWatcher(); - - /** - * Returns deep clone of DataWatcher modified. - * - * @return clone of DataWatcher modified - */ - default WrappedDataWatcher dataWatcherClone() { - return dataWatcher().deepClone(); - } - } -} diff --git a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/metadata/LatestDataWatcherFactory.java b/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/metadata/LatestDataWatcherFactory.java deleted file mode 100644 index 87b698662..000000000 --- a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/metadata/LatestDataWatcherFactory.java +++ /dev/null @@ -1,303 +0,0 @@ -package ru.progrm_jarvis.minecraft.nmsutils.metadata; - -import com.comphenix.protocol.wrappers.BlockPosition; -import com.comphenix.protocol.wrappers.WrappedDataWatcher; -import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry; -import com.comphenix.protocol.wrappers.WrappedWatchableObject; -import lombok.RequiredArgsConstructor; - -import java.util.Optional; -import java.util.UUID; - -/** - * DataWatcher factory for post 1.9 versions. - */ -public class LatestDataWatcherFactory implements DataWatcherFactory { - - protected static final WrappedDataWatcher.Serializer - BYTE_SERIALIZER = Registry.get(Byte.class), - INTEGER_SERIALIZER = Registry.get(Integer.class), - FLOAT_SERIALIZER = Registry.get(Float.class), - STRING_SERIALIZER = Registry.get(String.class), - I_CHAT_BASE_COMPONENT_SERIALIZER = Registry.getChatComponentSerializer(), - ITEM_STACK_SERIALIZER = Registry.getItemStackSerializer(false), - OPTIONAL_I_BLOCK_DATA_SERIALIZER = Registry.getBlockDataSerializer(true), - BOOLEAN_SERIALIZER = Registry.get(Boolean.class), - VECTOR_3F_SERIALIZER = Registry.getVectorSerializer(), - BLOCK_POSITION_SERIALIZER = Registry.getBlockPositionSerializer(false), - OPTIONAL_BLOCK_POSITION_SERIALIZER = Registry.getBlockPositionSerializer(true), - ENUM_DIRECTION_SERIALIZER = Registry.getDirectionSerializer(), - OPTIONAL_UUID_SERIALIZER = Registry.getUUIDSerializer(true), - NBT_TAG_COMPOUND_SERIALIZER = Registry.getNBTCompoundSerializer(); - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectByte(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, BYTE_SERIALIZER); - } - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectInteger(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, INTEGER_SERIALIZER); - } - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectFloat(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, FLOAT_SERIALIZER); - } - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectString(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, STRING_SERIALIZER); - } - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectIChatBaseComponent(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, I_CHAT_BASE_COMPONENT_SERIALIZER); - } - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectItemStack(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, ITEM_STACK_SERIALIZER); - } - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectOptionalIBlockData(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, OPTIONAL_I_BLOCK_DATA_SERIALIZER); - } - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectBoolean(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, BOOLEAN_SERIALIZER); - } - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectVector3f(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, VECTOR_3F_SERIALIZER); - } - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectBlockPosition(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, BLOCK_POSITION_SERIALIZER); - } - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectOptionalBlockPosition(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, OPTIONAL_BLOCK_POSITION_SERIALIZER); - } - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectEnumDirection(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, ENUM_DIRECTION_SERIALIZER); - } - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectOptionalUUID(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, OPTIONAL_UUID_SERIALIZER); - } - - protected WrappedDataWatcher.WrappedDataWatcherObject watcherObjectNBTTagCompound(final int id) { - return new WrappedDataWatcher.WrappedDataWatcherObject(id, NBT_TAG_COMPOUND_SERIALIZER); - } - - /////////////////////////////////////////////////////////////////////////// - // #createWatchableObject(id, value) - /////////////////////////////////////////////////////////////////////////// - - @Override - public WrappedWatchableObject createWatchable(final int id, final Byte value) { - return new WrappedWatchableObject(watcherObjectByte(id), value); - } - - @Override - public WrappedWatchableObject createWatchable(final int id, final Integer value) { - return new WrappedWatchableObject(watcherObjectInteger(id), value); - } - - @Override - public WrappedWatchableObject createWatchable(final int id, final Float value) { - return new WrappedWatchableObject(watcherObjectFloat(id), value); - } - - @Override - public WrappedWatchableObject createWatchable(final int id, final String value) { - return new WrappedWatchableObject(watcherObjectString(id), value); - } - - @Override - public WrappedWatchableObject createWatchableIChatBaseComponent(final int id, final Object value) { - return new WrappedWatchableObject(watcherObjectIChatBaseComponent(id), value); - } - - @Override - public WrappedWatchableObject createWatchableItemStack(final int id, final Object value) { - return new WrappedWatchableObject(watcherObjectItemStack(id), value); - } - - @Override - public WrappedWatchableObject createWatchableOptionalIBlockData(final int id, final Optional value) { - return new WrappedWatchableObject(watcherObjectOptionalIBlockData(id), value); - } - - @Override - public WrappedWatchableObject createWatchable(final int id, final Boolean value) { - return new WrappedWatchableObject(watcherObjectBoolean(id), value); - } - - @Override - public WrappedWatchableObject createWatchableVector3f(final int id, final Object value) { - return new WrappedWatchableObject(watcherObjectVector3f(id), value); - } - - @Override - public WrappedWatchableObject createWatchable(final int id, final BlockPosition value) { - return new WrappedWatchableObject(watcherObjectBlockPosition(id), value); - } - - @Override - public WrappedWatchableObject createWatchableOptionalBlockPosition(final int id, final Optional value) { - return new WrappedWatchableObject(watcherObjectOptionalBlockPosition(id), value); - } - - @Override - public WrappedWatchableObject createWatchableEnumDirection(final int id, final Object value) { - return new WrappedWatchableObject(watcherObjectEnumDirection(id), value); - } - - @Override - public WrappedWatchableObject createWatchableOptionalUUID(final int id, final Optional value) { - return new WrappedWatchableObject(watcherObjectOptionalUUID(id), value); - } - - @Override - public WrappedWatchableObject createWatchableNBTTagCompound(final int id, final Object value) { - return new WrappedWatchableObject(watcherObjectNBTTagCompound(id), value); - } - - @Override - public WrappedWatchableObject createWatchableObject(final int id, final Object value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public DataWatcherModifier modifier(WrappedDataWatcher watcher) { - return new DataWatcherModifier(watcher); - } - - @Override - public DataWatcherModifier modifier() { - return new DataWatcherModifier(); - } - - @RequiredArgsConstructor - private class DataWatcherModifier implements DataWatcherFactory.DataWatcherModifier { - - private final WrappedDataWatcher dataWatcher; - - private DataWatcherModifier() { - this(new WrappedDataWatcher()); - } - - @Override - @SuppressWarnings("MethodDoesntCallSuperMethod") - public DataWatcherFactory.DataWatcherModifier clone() { - return new DataWatcherModifier(dataWatcher.deepClone()); - } - - @Override - public DataWatcherModifier set(final int id, final Byte value) { - dataWatcher.setObject(watcherObjectByte(id), value); - - return this; - } - - @Override - public DataWatcherModifier set(final int id, final Integer value) { - dataWatcher.setObject(watcherObjectInteger(id), value); - - return this; - } - - @Override - public DataWatcherModifier set(final int id, final Float value) { - dataWatcher.setObject(watcherObjectFloat(id), value); - - return this; - } - - @Override - public DataWatcherModifier set(final int id, final String value) { - dataWatcher.setObject(watcherObjectString(id), value); - - return this; - } - - @Override - public DataWatcherModifier setIChatBaseComponent(final int id, final Object value) { - dataWatcher.setObject(watcherObjectIChatBaseComponent(id), value); - - return this; - } - - @Override - public DataWatcherModifier setItemStack(int id, Object value) { - dataWatcher.setObject(watcherObjectItemStack(id), value); - - return this; - } - - @Override - public DataWatcherModifier setOptionalIBlockData(final int id, final Optional value) { - dataWatcher.setObject(watcherObjectOptionalIBlockData(id), value); - - return this; - } - - @Override - public DataWatcherModifier set(final int id, final Boolean value) { - dataWatcher.setObject(watcherObjectBoolean(id), value); - - return this; - } - - @Override - public DataWatcherModifier setVector3f(final int id, final Object value) { - dataWatcher.setObject(watcherObjectVector3f(id), value); - - return this; - } - - @Override - public DataWatcherModifier set(final int id, final BlockPosition value) { - dataWatcher.setObject(watcherObjectBlockPosition(id), value); - - return this; - } - - @Override - public DataWatcherModifier setOptionalBlockPosition(final int id, final Optional value) { - dataWatcher.setObject(watcherObjectOptionalBlockPosition(id), value); - - return this; - } - - @Override - public DataWatcherModifier setEnumDirection(final int id, final Object value) { - dataWatcher.setObject(watcherObjectEnumDirection(id), value); - - return this; - } - - @Override - public DataWatcherModifier setOptionalUUID(final int id, final Optional value) { - dataWatcher.setObject(watcherObjectOptionalUUID(id), value); - - return this; - } - - @Override - public DataWatcherModifier setNBTTagCompound(final int id, final Object value) { - dataWatcher.setObject(watcherObjectNBTTagCompound(id), value); - - return this; - } - - @Override - public DataWatcherModifier setObject(final int id, final Object value) { - dataWatcher.setObject(id, value); - - return this; - } - - @Override - public WrappedDataWatcher dataWatcher() { - return dataWatcher; - } - } -} diff --git a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/metadata/LegacyDataWatcherFactory.java b/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/metadata/LegacyDataWatcherFactory.java deleted file mode 100644 index a4d158bdb..000000000 --- a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/metadata/LegacyDataWatcherFactory.java +++ /dev/null @@ -1,237 +0,0 @@ -package ru.progrm_jarvis.minecraft.nmsutils.metadata; - -import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.wrappers.BlockPosition; -import com.comphenix.protocol.wrappers.WrappedDataWatcher; -import com.comphenix.protocol.wrappers.WrappedWatchableObject; -import lombok.RequiredArgsConstructor; -import org.bukkit.inventory.ItemStack; - -import java.util.Optional; -import java.util.UUID; - -/** - * DataWatcher factory for pre 1.9 versions. - */ -public class LegacyDataWatcherFactory implements DataWatcherFactory { - - @Override - public DataWatcherModifier modifier(WrappedDataWatcher watcher) { - return new DataWatcherModifier(watcher); - } - - @Override - public DataWatcherModifier modifier() { - return new DataWatcherModifier(); - } - - @Override - public WrappedWatchableObject createWatchable(final int id, final Byte value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchable(final int id, final Integer value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchable(final int id, final Float value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchable(final int id, final String value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchableIChatBaseComponent(final int id, final Object value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchableItemStack(final int id, final Object value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchableOptionalIBlockData(final int id, final Optional value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchable(final int id, final Boolean value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchableVector3f(final int id, final Object value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchable(final int id, final BlockPosition value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchableOptionalBlockPosition(final int id, final Optional value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchableEnumDirection(final int id, final Object value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchableOptionalUUID(final int id, final Optional value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchableNBTTagCompound(final int id, final Object value) { - return new WrappedWatchableObject(id, value); - } - - @Override - public WrappedWatchableObject createWatchableObject(final int id, final Object value) { - return new WrappedWatchableObject(id, value); - } - - @RequiredArgsConstructor - private class DataWatcherModifier implements DataWatcherFactory.DataWatcherModifier { - - private final WrappedDataWatcher dataWatcher; - - private DataWatcherModifier() { - this(new WrappedDataWatcher()); - } - - @Override - @SuppressWarnings("MethodDoesntCallSuperMethod") - public DataWatcherFactory.DataWatcherModifier clone() { - return new DataWatcherModifier(dataWatcher.deepClone()); - } - - @Override - public WrappedDataWatcher dataWatcher() { - return dataWatcher; - } - - @Override - public DataWatcherModifier set(final int id, final Byte value) { - dataWatcher.setObject(id, value); - - return this; - } - - @Override - public DataWatcherModifier set(final int id, final Integer value) { - dataWatcher.setObject(id, value); - - return this; - } - - @Override - public DataWatcherModifier set(final int id, final Float value) { - dataWatcher.setObject(id, value); - - return this; - } - - @Override - public DataWatcherModifier set(final int id, final String value) { - dataWatcher.setObject(id, value); - - return this; - } - - @Override - public DataWatcherModifier setIChatBaseComponent(final int id, final Object value) { - dataWatcher.setObject(id, value); - - return this; - } - - - @Override - public DataWatcherModifier setItemStack(final int id, final Object value) { - dataWatcher.setObject(id, value); - - return this; - } - - @Override - public DataWatcherModifier set(final int id, final ItemStack value) { - dataWatcher.setObject(id, MinecraftReflection.getMinecraftItemStack(value)); - - return this; - } - - @Override - public DataWatcherModifier setOptionalIBlockData(final int id, final Optional value) { - dataWatcher.setObject(id, value); - - return this; - } - - @Override - public DataWatcherModifier set(final int id, final Boolean value) { - dataWatcher.setObject(id, value); - - return this; - } - - @Override - public DataWatcherModifier setVector3f(final int id, final Object value) { - dataWatcher.setObject(id, value); - - return this; - } - - @Override - public DataWatcherModifier set(final int id, final BlockPosition value) { - dataWatcher.setObject(id, value); - - return this; - } - - @Override - public DataWatcherModifier setOptionalBlockPosition(final int id, final Optional value) { - dataWatcher.setObject(id, value); - - return this; - } - - @Override - public DataWatcherModifier setEnumDirection(final int id, final Object value) { - dataWatcher.setObject(id, value); - - return this; - } - - @Override - public DataWatcherModifier setNBTTagCompound(final int id, final Object value) { - dataWatcher.setObject(id, value); - - return this; - } - - @Override - public DataWatcherModifier setOptionalUUID(final int id, final Optional value) { - dataWatcher.setObject(id, value); - - return this; - } - - // should be used if and only if none of default #setOptionalBlockPosition(id, value) methods don't provide type given - @Override - public DataWatcherModifier setObject(final int id, final Object value) { - dataWatcher.setObject(id, value); - - return this; - } - } -} diff --git a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/metadata/MetadataGenerator.java b/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/metadata/MetadataGenerator.java deleted file mode 100644 index e7017bff7..000000000 --- a/nms/src/main/java/ru/progrm_jarvis/minecraft/nmsutils/metadata/MetadataGenerator.java +++ /dev/null @@ -1,1158 +0,0 @@ -package ru.progrm_jarvis.minecraft.nmsutils.metadata; - -import com.comphenix.protocol.wrappers.*; -import com.comphenix.protocol.wrappers.EnumWrappers.Particle; -import com.comphenix.protocol.wrappers.nbt.NbtCompound; -import lombok.RequiredArgsConstructor; -import lombok.experimental.UtilityClass; -import lombok.val; -import lombok.var; -import org.bukkit.inventory.ItemStack; -import ru.progrm_jarvis.minecraft.nmsutils.Conversions; -import ru.progrm_jarvis.minecraft.nmsutils.NmsUtil; - -import java.util.Optional; -import java.util.UUID; - -/** - * Editor for Metadata of {@link WrappedWatchableObject} providing classes - * containing static methods for developer-friendly object creation. - */ -@UtilityClass -public class MetadataGenerator { - - private final int VERSION = NmsUtil.getVersion().getGeneration(); - private final DataWatcherFactory FACTORY = NmsUtil.getDataWatcherFactory(); - - public static class Entity { - - public static WrappedWatchableObject entityFlags(final Flag... flags) { - var flagBytes = (byte) 0; - for (val flag : flags) flagBytes |= flag.value; - - return FACTORY.createWatchable(0, flagBytes); - } - - public static WrappedWatchableObject air(final boolean air) { - return FACTORY.createWatchable(1, air); - } - - public static WrappedWatchableObject name(final WrappedChatComponent name) { - return FACTORY.createWatchable(2, name); - } - - public static WrappedWatchableObject name(final String name) { - if (VERSION > 13) return name(WrappedChatComponent.fromText(name)); - return FACTORY.createWatchable(2, name); - } - - public static WrappedWatchableObject nameVisible(final boolean nameVisible) { - return FACTORY.createWatchable(3, nameVisible); - } - - public static WrappedWatchableObject silent(final boolean silent) { - return FACTORY.createWatchable(4, silent); - } - - public static WrappedWatchableObject noGravity(final boolean noGravity) { - return FACTORY.createWatchable(5, noGravity); - } - - @RequiredArgsConstructor - public enum Flag { - ON_FIRE((byte) 0x01), - CROUCHED((byte) 0x02), - RIDING((byte) 0x04), - SPRINTING((byte) 0x08), - SWIMMING((byte) 0x10), - INVISIBLE((byte) 0x20), - GLOWING((byte) 0x80); - - private final byte value; - } - } - - public static class Projectile extends Entity {} - - public static class Snowball extends Projectile {} - - public static class Egg extends Projectile {} - - public static class Potion extends Projectile { - - public static WrappedWatchableObject potion(final Object potion) { - return FACTORY.createWatchableItemStack(6, potion); - } - - public static WrappedWatchableObject potion(final ItemStack potion) { - return FACTORY.createWatchable(6, potion); - } - } - - public static class FallingBlock extends Entity { - - public static WrappedWatchableObject position(final BlockPosition position) { - return FACTORY.createWatchable(6, position); - } - } - - public static class AreaEffectCloud extends Entity { - - public static WrappedWatchableObject radius(final float radius) { - return FACTORY.createWatchableItemStack(6, radius); - } - - public static WrappedWatchableObject color(final int color) { - return FACTORY.createWatchable(7, color); - } - - public static WrappedWatchableObject singlePoint(final boolean singlePoint) { - return FACTORY.createWatchable(8, singlePoint); - } - - public static WrappedWatchableObject singlePoint(final Particle particle) { - // unsure (?) - return FACTORY.createWatchableObject(9, Conversions.toNms(particle)); - } - } - - public static class FishingHook extends Entity { - - public static WrappedWatchableObject hookedEntity(final int hookedEntityId) { - return FACTORY.createWatchable(6, hookedEntityId); - } - - public static WrappedWatchableObject hookedEntity(final org.bukkit.entity.Entity entity) { - return hookedEntity(entity.getEntityId() + 1); - } - } - - public static class Arrow extends Entity { - - public static WrappedWatchableObject arrowFlags(final Flag... flags) { - var flagBytes = (byte) 0; - for (val flag : flags) flagBytes |= flag.value; - - return FACTORY.createWatchable(6, flagBytes); - } - - public static WrappedWatchableObject shooter(final UUID shooterUuid) { - return FACTORY.createWatchableOptionalUUID(7, Optional.ofNullable(shooterUuid)); - } - - @RequiredArgsConstructor - public enum Flags { - CRITICAL((byte) 0x01); - - private final byte value; - } - } - - public static class TippedArrow extends Arrow { - - public static WrappedWatchableObject color(final int color) { - return FACTORY.createWatchable(8, color); - } - } - - public static class Trident extends Arrow { - - public static WrappedWatchableObject loyaltyLevel(final int loyaltyLevel) { - return FACTORY.createWatchable(8, loyaltyLevel); - } - } - - public static class Boat extends Entity { - - public static WrappedWatchableObject timeSinceLastHit(final int timeSinceLastHit) { - return FACTORY.createWatchable(6, timeSinceLastHit); - } - - public static WrappedWatchableObject forwardDirection(final int forwardDirection) { - return FACTORY.createWatchable(7, forwardDirection); - } - - public static WrappedWatchableObject damageTaken(final float damageTaken) { - return FACTORY.createWatchable(8, damageTaken); - } - - public static WrappedWatchableObject type(final Type type) { - return FACTORY.createWatchable(9, type.value); - } - - public static WrappedWatchableObject rightPaddleTurning(final boolean rightPaddleTurning) { - return FACTORY.createWatchable(10, rightPaddleTurning); - } - - public static WrappedWatchableObject leftPaddleTurning(final boolean leftPaddleTurning) { - return FACTORY.createWatchable(11, leftPaddleTurning); - } - - public static WrappedWatchableObject splashTimer(final int splashTimer) { - return FACTORY.createWatchable(12, splashTimer); - } - - @RequiredArgsConstructor - public enum Type { - OAK((byte) 0), - SPRUCE((byte) 1), - BIRCH((byte) 2), - JUNGLE((byte) 3), - ACACIA((byte) 4), - DARK_OAK((byte) 5); - - private final byte value; - } - } - - public static class EnderCrystal extends Entity { - - public static WrappedWatchableObject position(final BlockPosition position) { - return FACTORY.createWatchableOptionalBlockPosition(6, Optional.of(position)); - } - - public static WrappedWatchableObject showBottom(final boolean showBottom) { - return FACTORY.createWatchable(7, showBottom); - } - } - - public static class Fireball extends Entity {} - - public static class WitherSkull extends Entity { - - public static WrappedWatchableObject invulnerable(final boolean invulnerable) { - return FACTORY.createWatchable(6, invulnerable); - } - } - - public static class Fireworks extends Entity { - - public static WrappedWatchableObject item(final Object nmsItem) { - return FACTORY.createWatchableItemStack(6, nmsItem); - } - - public static WrappedWatchableObject item(final ItemStack item) { - return FACTORY.createWatchable(6, item); - } - - public static WrappedWatchableObject shooter(final int shooter) { - return FACTORY.createWatchable(6, shooter); - } - - public static WrappedWatchableObject shooter(final org.bukkit.entity.Entity entity) { - return shooter(entity.getEntityId()); - } - } - - public static class Hanging extends Entity {} - - public static class ItemFrame extends Hanging { - - public static WrappedWatchableObject item(final Object nmsItem) { - return FACTORY.createWatchableItemStack(6, nmsItem); - } - - public static WrappedWatchableObject item(final ItemStack item) { - return FACTORY.createWatchable(6, item); - } - - public static WrappedWatchableObject rotation(final int rotation) { - return FACTORY.createWatchable(7, rotation); - } - } - - public static class Item extends Entity { - - public static WrappedWatchableObject item(final Object nmsItem) { - return FACTORY.createWatchableItemStack(6, nmsItem); - } - - public static WrappedWatchableObject item(final ItemStack item) { - return FACTORY.createWatchable(6, item); - } - } - - public static class Living extends Entity { - - public static WrappedWatchableObject handStates(final HandState... handStates) { - var handStateBytes = (byte) 0; - for (val handState : handStates) handStateBytes |= handState.value; - - return FACTORY.createWatchableObject(6, handStateBytes); - } - - public static WrappedWatchableObject health(final float health) { - return FACTORY.createWatchable(7, health); - } - - public static WrappedWatchableObject potionEffectColor(final int potionEffectColor) { - return FACTORY.createWatchable(8, potionEffectColor); - } - - public static WrappedWatchableObject potionEffectAmbient(final boolean potionEffectAmbient) { - return FACTORY.createWatchable(9, potionEffectAmbient); - } - - public static WrappedWatchableObject numberOfArrows(final int numberOfArrows) { - return FACTORY.createWatchable(10, numberOfArrows); - } - - @RequiredArgsConstructor - public enum HandState { - HAND_ACTIVE((byte) 0x01), - OFFHAND((byte) 0x02), - RIPTIDE_SPIN_ATTACK((byte) 0x04); - - private final byte value; - } - } - - public static class Player extends Living { - - public static WrappedWatchableObject additionalHearts(final float additionalHearts) { - return FACTORY.createWatchable(11, additionalHearts); - } - - public static WrappedWatchableObject score(final int score) { - return FACTORY.createWatchable(12, score); - } - - public static WrappedWatchableObject skinParts(final SkinPart... skinParts) { - var skinPartBytes = (byte) 0; - for (val skinPart : skinParts) skinPartBytes |= skinPart.value; - - return FACTORY.createWatchableObject(13, skinPartBytes); - } - - public static WrappedWatchableObject mainHand(final MainHand mainHand) { - return FACTORY.createWatchable(14, mainHand.value); - } - - public static WrappedWatchableObject leftShoulderEntity(final Object leftShoulderEntityNbtTagCompound) { - return FACTORY.createWatchableObject(15, leftShoulderEntityNbtTagCompound); - } - - public static WrappedWatchableObject leftShoulderEntity(final NbtCompound leftShoulderEntityNbt) { - return leftShoulderEntity(leftShoulderEntityNbt.getHandle()); - } - - public static WrappedWatchableObject rightShoulderEntity(final Object rightShoulderEntityNbtTagCompound) { - return FACTORY.createWatchableNBTTagCompound(16, rightShoulderEntityNbtTagCompound); - } - - public static WrappedWatchableObject rightShoulderEntity(final NbtCompound rightShoulderEntityNbt) { - return rightShoulderEntity(rightShoulderEntityNbt.getHandle()); - } - - @RequiredArgsConstructor - public enum SkinPart { - CAPE((byte) 0x01), - JACKET((byte) 0x02), - LEFT_SLEEVE((byte) 0x04), - RIGHT_SLEEVE((byte) 0x08), - LEFT_PANT((byte) 0x10), - RIGHT_PANT((byte) 0x20), - HAT((byte) 0x40), - UNUSED((byte) 0x80); - - private final byte value; - } - - @RequiredArgsConstructor - public enum MainHand { - LEFT((byte) 0), - RIGHT((byte) 1); - - private final byte value; - } - } - - public static class ArmorStand extends Living { - - public static WrappedWatchableObject armorStandFlags(final Flag... flags) { - var flagBytes = (byte) 0; - for (val flag : flags) flagBytes |= flag.value; - - return FACTORY.createWatchable(11, flagBytes); - } - - public static WrappedWatchableObject headRotation(final Vector3F headRotation) { - return FACTORY.createWatchable(12, headRotation); - } - - public static WrappedWatchableObject bodyRotation(final Vector3F bodyRotation) { - return FACTORY.createWatchable(13, bodyRotation); - } - - public static WrappedWatchableObject leftArmRotation(final Vector3F leftArmRotation) { - return FACTORY.createWatchable(14, leftArmRotation); - } - - public static WrappedWatchableObject rightArmRotation(final Vector3F rightArmRotation) { - return FACTORY.createWatchable(15, rightArmRotation); - } - - public static WrappedWatchableObject leftLegRotation(final Vector3F leftLegRotation) { - return FACTORY.createWatchable(16, leftLegRotation); - } - - public static WrappedWatchableObject rightLegRotation(final Vector3F rightLegRotation) { - return FACTORY.createWatchable(17, rightLegRotation); - } - - @RequiredArgsConstructor - public enum Flag { - SMALL((byte) 0x01), - HAS_ARMS((byte) 0x04), - NO_BASE_PLATE((byte) 0x08), - MARKER((byte) 0x10); - - private final byte value; - } - } - - public static class Insentient extends Living { - - public static WrappedWatchableObject insentientFlags(final Flag... flags) { - var flagBytes = (byte) 0; - for (val flag : flags) flagBytes |= flag.value; - - return FACTORY.createWatchable(11, flagBytes); - } - - @RequiredArgsConstructor - public enum Flag { - NO_AI((byte) 0x01), - LEFT_HANDED((byte) 0x02); - - private final byte value; - } - } - - public static class Ambient extends Insentient {} - - public static class Bat extends Ambient { - - public static WrappedWatchableObject batFlags(final Flag... flags) { - var flagBytes = (byte) 0; - for (val flag : flags) flagBytes |= flag.value; - - return FACTORY.createWatchable(12, flagBytes); - } - - @RequiredArgsConstructor - public enum Flag { - HANGING((byte) 0x01); - - private final byte value; - } - } - - public static class Creature extends Insentient {} - - public static class WaterMob extends Creature {} - - public static class Squid extends WaterMob {} - - public static class Dolphin extends WaterMob { - - public static WrappedWatchableObject treasurePosition(final BlockPosition treasurePosition) { - return FACTORY.createWatchable(12, treasurePosition); - } - - public static WrappedWatchableObject canFindTreasure(final boolean canFindTreasure) { - return FACTORY.createWatchable(13, canFindTreasure); - } - - public static WrappedWatchableObject hasFish(final boolean hasFish) { - return FACTORY.createWatchable(14, hasFish); - } - } - - public static class Fish extends WaterMob { - - public static WrappedWatchableObject fromBucket(final boolean fromBucket) { - return FACTORY.createWatchable(12, fromBucket); - } - } - - public static class Cod extends Fish {} - - public static class PufferFish extends Fish { - - public static WrappedWatchableObject puffState(final int puffState) { - return FACTORY.createWatchable(13, puffState); - } - } - - public static class Salmon extends Fish {} - - public static class TropicalFish extends Fish { - - public static WrappedWatchableObject variant(final int variant) { - return FACTORY.createWatchable(13, variant); - } - } - - public static class Ageable extends Creature { - - public static WrappedWatchableObject baby(final boolean baby) { - return FACTORY.createWatchable(12, baby); - } - } - - public static class Animal extends Ageable {} - - public static class AbstractHorse extends Animal { - - public static WrappedWatchableObject horseFlags(final Flag... flags) { - var flagBytes = (byte) 0; - for (val flag : flags) flagBytes |= flag.value; - - return FACTORY.createWatchable(13, flagBytes); - } - - public static WrappedWatchableObject owner(final UUID ownerUuid) { - return FACTORY.createWatchableOptionalUUID(14, Optional.ofNullable(ownerUuid)); - } - - @RequiredArgsConstructor - public enum Flag { - UNUSED_1((byte) 0x01), - TAME((byte) 0x02), - SADDLED((byte) 0x04), - BRED((byte) 0x08), - EATING((byte) 0x10), - REARING((byte) 0x20), - MOUTH_OPEN((byte) 0x40), - UNUSED_2((byte) 0x80); - - private final byte value; - } - } - - public static class Horse extends AbstractHorse { - - public static WrappedWatchableObject variant(final int variant) { - return FACTORY.createWatchable(15, variant); - } - - public static WrappedWatchableObject armor(final Armor armor) { - return FACTORY.createWatchable(16, armor.value); - } - - public static WrappedWatchableObject forgeArmor(final Object nmsItem) { - return FACTORY.createWatchableItemStack(17, nmsItem); - } - - public static WrappedWatchableObject forgeArmor(final ItemStack item) { - return FACTORY.createWatchable(17, item); - } - - @RequiredArgsConstructor - public enum Armor { - NONE(0), - IRON(1), - GOLD(2), - DIAMOND(3); - - private final int value; - } - } - - public static class ZombieHorse extends AbstractHorse {} - - public static class SkeletonHorse extends AbstractHorse {} - - public static class ChestedHorse extends AbstractHorse { - - public static WrappedWatchableObject chest(final boolean chest) { - return FACTORY.createWatchable(15, chest); - } - } - - public static class Donkey extends ChestedHorse {} - - public static class Llama extends ChestedHorse { - - public static WrappedWatchableObject strength(final int strength) { - return FACTORY.createWatchable(16, strength); - } - - public static WrappedWatchableObject carpetColor(final int carpetColor) { - return FACTORY.createWatchable(17, carpetColor); - } - - public static WrappedWatchableObject variant(final Variant variant) { - return FACTORY.createWatchable(18, variant.value); - } - - @RequiredArgsConstructor - public enum Variant { - CREAMY((byte) 0), - WHITE((byte) 1), - BROWN((byte) 2), - GRAY((byte) 3); - - private final byte value; - } - } - - public static class Mule extends ChestedHorse {} - - public static class Pig extends Animal { - - public static WrappedWatchableObject saddle(final boolean saddle) { - return FACTORY.createWatchable(13, saddle); - } - - public static WrappedWatchableObject boostTime(final int boostTime) { - return FACTORY.createWatchable(14, boostTime); - } - } - - public static class Rabbit extends Animal { - - public static WrappedWatchableObject type(final int type) { - return FACTORY.createWatchable(13, type); - } - } - - public static class Turtle extends Animal { - - public static WrappedWatchableObject home(final BlockPosition home) { - return FACTORY.createWatchable(13, home); - } - - public static WrappedWatchableObject hasEgg(final boolean hasEgg) { - return FACTORY.createWatchable(14, hasEgg); - } - - public static WrappedWatchableObject layingEgg(final boolean layingEgg) { - return FACTORY.createWatchable(15, layingEgg); - } - - public static WrappedWatchableObject travelPosition(final BlockPosition travelPosition) { - return FACTORY.createWatchable(16, travelPosition); - } - - public static WrappedWatchableObject goingHome(final boolean goingHome) { - return FACTORY.createWatchable(17, goingHome); - } - - public static WrappedWatchableObject travelling(final boolean travelling) { - return FACTORY.createWatchable(18, travelling); - } - } - - public static class PolarBear extends Animal { - - public static WrappedWatchableObject standingUp(final boolean standingUp) { - return FACTORY.createWatchable(13, standingUp); - } - } - - public static class Sheep extends Animal { - - public static WrappedWatchableObject standingUp(final byte color, final boolean sheared) { - return FACTORY.createWatchable(13, color & 0x0F | (sheared ? 0 : 0x10)); - } - } - - public static class Tameable extends Animal { - - public static WrappedWatchableObject tameableFlags(final Flag... flags) { - var flagBytes = (byte) 0; - for (val flag : flags) flagBytes |= flag.value; - - return FACTORY.createWatchable(13, flagBytes); - } - - public static WrappedWatchableObject owner(final UUID ownerUuid) { - return FACTORY.createWatchableOptionalUUID(14, Optional.ofNullable(ownerUuid)); - } - - @RequiredArgsConstructor - public enum Flag { - SITTING((byte) 0x01), - ANGRY((byte) 0x02), - TAMED((byte) 0x04); - - private final byte value; - } - } - - public static class Ocelot extends Tameable { - - public static WrappedWatchableObject variant(final Variant variant) { - return FACTORY.createWatchable(15, variant.value); - } - - @RequiredArgsConstructor - public enum Variant { - UNTAMED(0), - TUXEDO(1), - TABBY(2), - SIAMESE(3); - - private final int value; - } - } - - public static class Wolf extends Tameable { - - public static WrappedWatchableObject damageTaken(final float damageTaken) { - return FACTORY.createWatchable(15, damageTaken); - } - - public static WrappedWatchableObject begging(final boolean begging) { - return FACTORY.createWatchable(16, begging); - } - - public static WrappedWatchableObject collarColor(final byte collarColor) { - return FACTORY.createWatchable(17, collarColor); - } - } - - public static class Parrot extends Tameable { - - public static WrappedWatchableObject variant(final Variant variant) { - return FACTORY.createWatchable(15, variant.value); - } - - @RequiredArgsConstructor - public enum Variant { - RED_BLUE(0), - BLUE(1), - GREEN(2), - YELLOW_BLUE(3), - SILVER(4); - - private final int value; - } - } - - public static class Villager extends Ageable { - - public static WrappedWatchableObject profession(final Profession profession) { - return FACTORY.createWatchable(13, profession.value); - } - - @RequiredArgsConstructor - public enum Profession { - FARMER(0), - LIBRARIAN(1), - PRIEST(2), - BLACKSMITH(3), - SILVER(4); - - private final int value; - } - } - - public static class Golem extends Creature {} - - public static class IronGolem extends Golem { - - public static WrappedWatchableObject ironGolemFlags(final Flag... flags) { - var flagBytes = (byte) 0; - for (val flag : flags) flagBytes |= flag.value; - - return FACTORY.createWatchable(12, flagBytes); - } - - @RequiredArgsConstructor - public enum Flag { - PLAYER_CREATED((byte) 0x01); - - private final byte value; - } - } - - public static class Snowman extends Golem { - - public static WrappedWatchableObject ironGolemFlags(final Flag... flags) { - var flagBytes = (byte) 0; - for (val flag : flags) flagBytes |= flag.value; - - return FACTORY.createWatchable(12, flagBytes); - } - - @RequiredArgsConstructor - public enum Flag { - HAS_PUMPKIN((byte) 0x10); - - private final byte value; - } - } - - public static class Shulker extends Golem { - - public static WrappedWatchableObject facing(final Object enumDirection) { - return FACTORY.createWatchableEnumDirection(12, enumDirection); - } - - public static WrappedWatchableObject facing(final EnumWrappers.Direction direction) { - return FACTORY.createWatchable(12, direction); - } - - public static WrappedWatchableObject attachmentPosition(final BlockPosition attachmentPosition) { - return FACTORY.createWatchableOptionalBlockPosition(13, Optional.ofNullable(attachmentPosition)); - } - - public static WrappedWatchableObject shieldHeight(final byte shieldHeight) { - return FACTORY.createWatchable(14, shieldHeight); - } - - public static WrappedWatchableObject color(final byte color) { - return FACTORY.createWatchable(15, color); - } - } - - public static class Monster extends Creature {} - - public static class Blaze extends Monster { - - public static WrappedWatchableObject blazeFlags(final Flag... flags) { - var flagBytes = (byte) 0; - for (val flag : flags) flagBytes |= flag.value; - - return FACTORY.createWatchable(12, flagBytes); - } - - @RequiredArgsConstructor - public enum Flag { - ON_FIRE((byte) 0x01); - - private final byte value; - } - } - - public static class Creeper extends Monster { - - public static WrappedWatchableObject creeperState(final State state) { - return FACTORY.createWatchable(12, state.value); - } - - public static WrappedWatchableObject charged(final boolean charged) { - return FACTORY.createWatchable(13, charged); - } - - public static WrappedWatchableObject ignited(final boolean ignited) { - return FACTORY.createWatchable(14, ignited); - } - - @RequiredArgsConstructor - public enum State { - IDLE(-1), - FUSE(1); - - private final int value; - } - } - - public static class Endermite extends Monster {} - - public static class GiantZombie extends Monster {} - - public static class Guardian extends Monster { - - public static WrappedWatchableObject retractingSpikes(final boolean retractingSpikes) { - return FACTORY.createWatchable(12, retractingSpikes); - } - - public static WrappedWatchableObject targetEntity(final int targetEntityId) { - return FACTORY.createWatchable(13, targetEntityId); - } - - public static WrappedWatchableObject targetEntity(final org.bukkit.entity.Entity entity) { - return targetEntity(entity.getEntityId()); - } - } - - public static class ElderGuardian extends Guardian {} - - public static class Silverfish extends Monster {} - - public static class Illager extends Monster { - - public static WrappedWatchableObject illagerState(final State state) { - return FACTORY.createWatchable(12, state.value); - } - - @RequiredArgsConstructor - public enum State { - HAS_TARGET((byte) 0x01); - - private final byte value; - } - } - - public static class VindicatorIllager extends Illager {} - - public static class SpellcasterIllager extends Illager { - - public static WrappedWatchableObject spell(final Spell spell) { - return FACTORY.createWatchable(13, spell.value); - } - - @RequiredArgsConstructor - public enum Spell { - NONE((byte) 0), - SUMMON_VEX((byte) 1), - ATTACK((byte) 2), - WOLOLO((byte) 3); - - private final byte value; - } - } - - public static class EvocationIllager extends SpellcasterIllager {} - - public static class IllusionIllager extends SpellcasterIllager {} - - public static class Vex extends Monster { - - public static WrappedWatchableObject vexFlags(final Flag... flags) { - var flagBytes = (byte) 0; - for (val flag : flags) flagBytes |= flag.value; - - return FACTORY.createWatchable(12, flagBytes); - } - - @RequiredArgsConstructor - public enum Flag { - ATTACK_MODE((byte) 0x01); - - private final byte value; - } - } - - public static class EvocationFangs extends Entity {} - - public static class AbstractSkeleton extends Monster { - - public static WrappedWatchableObject swingingArms(final boolean swingingArms) { - return FACTORY.createWatchable(12, swingingArms); - } - } - - public static class Skeleton extends AbstractSkeleton {} - - public static class WitherSkeleton extends AbstractSkeleton {} - - public static class Stray extends AbstractSkeleton {} - - public static class Spider extends Monster { - - public static WrappedWatchableObject spiderFlags(final Flag... flags) { - var flagBytes = (byte) 0; - for (val flag : flags) flagBytes |= flag.value; - - return FACTORY.createWatchable(12, flagBytes); - } - - @RequiredArgsConstructor - public enum Flag { - CLIMBING((byte) 0x01); - - private final byte value; - } - } - - public static class Witch extends Monster { - - public static WrappedWatchableObject drinkingPotion(final boolean drinkingPotion) { - return FACTORY.createWatchable(12, drinkingPotion); - } - } - - public static class Wither extends Monster { - - public static WrappedWatchableObject centerHeadTarget(final int centerHeadTargetId) { - return FACTORY.createWatchable(12, centerHeadTargetId); - } - - public static WrappedWatchableObject centerHeadTarget(final org.bukkit.entity.Entity centerHeadTarget) { - return centerHeadTarget(centerHeadTarget == null ? 0 : centerHeadTarget.getEntityId()); - } - - public static WrappedWatchableObject leftHeadTarget(final int leftHeadTargetId) { - return FACTORY.createWatchable(13, leftHeadTargetId); - } - - public static WrappedWatchableObject leftHeadTarget(final org.bukkit.entity.Entity leftHeadTarget) { - return leftHeadTarget(leftHeadTarget == null ? 0 : leftHeadTarget.getEntityId()); - } - - public static WrappedWatchableObject rightHeadTarget(final int rightHeadTargetId) { - return FACTORY.createWatchable(14, rightHeadTargetId); - } - - public static WrappedWatchableObject rightHeadTarget(final org.bukkit.entity.Entity rightHeadTarget) { - return rightHeadTarget(rightHeadTarget == null ? 0 : rightHeadTarget.getEntityId()); - } - - public static WrappedWatchableObject invulnerableTime(final int invulnerableTime) { - return FACTORY.createWatchable(15, invulnerableTime); - } - } - - public static class Zombie extends Monster { - - public static WrappedWatchableObject baby(final boolean baby) { - return FACTORY.createWatchable(12, baby); - } - - public static WrappedWatchableObject legacyType(final int legacyType) { - return FACTORY.createWatchable(13, legacyType); - } - - public static WrappedWatchableObject handsUp(final boolean handsUp) { - return FACTORY.createWatchable(14, handsUp); - } - - public static WrappedWatchableObject becomingDrowned(final boolean becomingDrowned) { - return FACTORY.createWatchable(15, becomingDrowned); - } - } - - public static class ZombieVillager extends Zombie { - - public static WrappedWatchableObject converting(final boolean converting) { - return FACTORY.createWatchable(16, converting); - } - - public static WrappedWatchableObject profession(final Villager.Profession profession) { - return FACTORY.createWatchable(17, profession.value); - } - } - - public static class Husk extends Zombie {} - - public static class Drowned extends Zombie {} - - public static class Enderman extends Monster { - - public static WrappedWatchableObject carriedBlock(final Object carriedBlock) { - return FACTORY.createWatchableOptionalIBlockData(12, Optional.of(carriedBlock)); - } - - public static WrappedWatchableObject screaming(final boolean screaming) { - return FACTORY.createWatchable(13, screaming); - } - } - - public static class EnderDragon extends Monster { - - public static WrappedWatchableObject phase(final Phase phase) { - return FACTORY.createWatchable(12, phase.value); - } - - @RequiredArgsConstructor - public enum Phase { - CIRCLING(0), - STRAFING(1), - FLYING(2), - LANDING(3), - TAKING_OFF(4), - BREATHING(5), - LOOKING_FOR_PLAYER(6), - ROARING(7), - CHARGING_PLAYER(8), - FLYDYING(9), - NO_AI(10); - - private final int value; - } - } - - public static class Flying extends Insentient {} - - public static class Ghast extends Flying { - - public static WrappedWatchableObject attacking(final boolean attacking) { - return FACTORY.createWatchable(12, attacking); - } - } - - public static class Phantom extends Flying { - - public static WrappedWatchableObject size(final int size) { - return FACTORY.createWatchable(12, size); - } - } - - public static class Slime extends Insentient { - - public static WrappedWatchableObject size(final int size) { - return FACTORY.createWatchable(12, size); - } - } - - public static class LlamaSpit extends Entity {} - - public static class Minecart extends Entity { - - public static WrappedWatchableObject shakingPower(final int shakingPower) { - return FACTORY.createWatchable(6, shakingPower); - } - - public static WrappedWatchableObject shakingDirection(final int shakingDirection) { - return FACTORY.createWatchable(7, shakingDirection); - } - - public static WrappedWatchableObject shakingMultiplier(final int shakingMultiplier) { - return FACTORY.createWatchable(8, shakingMultiplier); - } - - public static WrappedWatchableObject customBlockIdAndDamage(final int customBlockIdAndDamage) { - return FACTORY.createWatchable(9, customBlockIdAndDamage); - } - - public static WrappedWatchableObject customBlockY(final int customBlockY) { - return FACTORY.createWatchable(10, customBlockY); - } - - public static WrappedWatchableObject showCustomBlock(final boolean showCustomBlock) { - return FACTORY.createWatchable(11, showCustomBlock); - } - } - - public static class MinecartRideable extends Minecart {} - - public static class MinecartContainer extends Minecart {} - - public static class MinecartHopper extends Minecart {} - - public static class MinecartChest extends Minecart {} - - public static class MinecartFurnace extends Minecart { - - public static WrappedWatchableObject powered(final boolean powered) { - return FACTORY.createWatchable(12, powered); - } - } - - public static class MinecartTnt extends Minecart {} - - public static class MinecartSpawner extends Minecart {} - - public static class MinecartCommandBlock extends Minecart { - - public static WrappedWatchableObject command(final String command) { - return FACTORY.createWatchable(12, command); - } - - public static WrappedWatchableObject lastOutput(final WrappedChatComponent lastOutput) { - return FACTORY.createWatchable(13, lastOutput); - } - } - - public static class TNTPrimed extends Entity { - - public static WrappedWatchableObject fuseTime(final int fuseTime) { - return FACTORY.createWatchable(6, fuseTime); - } - } -} diff --git a/nms/src/test/java/org/bukkit/craftbukkit/v1_12_R1/CraftEntity.java b/nms/src/test/java/org/bukkit/craftbukkit/v1_12_R1/CraftEntity.java deleted file mode 100644 index aff3e70e7..000000000 --- a/nms/src/test/java/org/bukkit/craftbukkit/v1_12_R1/CraftEntity.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.bukkit.craftbukkit.v1_12_R1; - -public class CraftEntity { - - private int entityCount = 0; -} diff --git a/nms/src/test/java/org/bukkit/craftbukkit/v1_12_R1/Entity.java b/nms/src/test/java/org/bukkit/craftbukkit/v1_12_R1/Entity.java deleted file mode 100644 index 417b22891..000000000 --- a/nms/src/test/java/org/bukkit/craftbukkit/v1_12_R1/Entity.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.bukkit.craftbukkit.v1_12_R1; - -public class Entity { - - private int entityCount = 0; -} diff --git a/nms/src/test/java/org/bukkit/craftbukkit/v1_12_R1/ServerTestImpl.java b/nms/src/test/java/org/bukkit/craftbukkit/v1_12_R1/ServerTestImpl.java deleted file mode 100644 index 9e05b84dd..000000000 --- a/nms/src/test/java/org/bukkit/craftbukkit/v1_12_R1/ServerTestImpl.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.bukkit.craftbukkit.v1_12_R1; - -import org.bukkit.Server; - -public interface ServerTestImpl extends Server { -} diff --git a/nms/src/test/java/ru/progrm_jarvis/minecraft/nmsutils/NmsUtilTest.java b/nms/src/test/java/ru/progrm_jarvis/minecraft/nmsutils/NmsUtilTest.java deleted file mode 100644 index c5d0304dc..000000000 --- a/nms/src/test/java/ru/progrm_jarvis/minecraft/nmsutils/NmsUtilTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package ru.progrm_jarvis.minecraft.nmsutils; - -class NmsUtilTest { - - /* - TODO find solution for effective mocking - - static final short GENERATION = 12; - static final String VERSION_NAME = "v1_" + GENERATION + "_R1"; - - @BeforeEach - void setUp() { - val server = mock(ServerTestImpl.class); - - val logger = mock(Logger.class); - when(server.getLogger()).thenReturn(logger); - - Bukkit.setServer(server); - } - - @Test - void getVersion() { - val version = NmsUtil.getVersion(); - - assertEquals(version.getName(), VERSION_NAME); - assertEquals(version.getGeneration(), 12); - assertEquals(new NmsUtil.NmsVersion(VERSION_NAME, GENERATION), version); - } - - @Test - void getNmsPackage() { - } - - @Test - void getCraftBukkitPackage() { - } - - @Test - void getDataWatcherFactory() { - } - - @Test - void dataWatcherModifier() { - } - - @Test - void dataWatcherModifier1() { - } - - @Test - void nextEntityId() { - } - - */ -} \ No newline at end of file diff --git a/nms/src/test/java/ru/progrm_jarvis/minecraft/nmsutils/ToStringUtilTest.java b/nms/src/test/java/ru/progrm_jarvis/minecraft/nmsutils/ToStringUtilTest.java deleted file mode 100644 index ab969c2a8..000000000 --- a/nms/src/test/java/ru/progrm_jarvis/minecraft/nmsutils/ToStringUtilTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.progrm_jarvis.minecraft.nmsutils; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class ToStringUtilTest { - - @Test - void getterNameToStringTest() { - assertEquals("lel", ToStringUtil.getterNameToString("lel")); - assertEquals("foo", ToStringUtil.getterNameToString("getFoo")); - assertEquals("barBaz", ToStringUtil.getterNameToString("getBarBaz")); - assertEquals("get", ToStringUtil.getterNameToString("get")); - } -} \ No newline at end of file diff --git a/player/pom.xml b/player/pom.xml deleted file mode 100644 index adc265000..000000000 --- a/player/pom.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - 4.0.0 - - minecraft-utils - ru.progrm-jarvis.minecraft - 0.1.0-SNAPSHOT - - player-utils - - - - org.spigotmc - spigot-api - - - - org.projectlombok - lombok - - - \ No newline at end of file diff --git a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/PlayerUtil.java b/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/PlayerUtil.java deleted file mode 100644 index ce840cdf9..000000000 --- a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/PlayerUtil.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.progrm_jarvis.minecraft.playerutils; - -import lombok.NonNull; -import lombok.experimental.UtilityClass; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.entity.Player; - -import java.util.Collection; -import java.util.stream.Collectors; - -@UtilityClass -public class PlayerUtil { - - public Collection playersAround(@NonNull final Location location, final double radius) { - return Bukkit.getOnlinePlayers().stream() - .filter(player -> player.getLocation().distance(location) <= radius) - .collect(Collectors.toList()); - } - - public Collection playerEyesAround(@NonNull final Location location, final double radius) { - return Bukkit.getOnlinePlayers().stream() - .filter(player -> player.getEyeLocation().distance(location) <= radius) - .collect(Collectors.toList()); - } -} diff --git a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/collection/PlayerContainers.java b/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/collection/PlayerContainers.java deleted file mode 100644 index be7745caa..000000000 --- a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/collection/PlayerContainers.java +++ /dev/null @@ -1,76 +0,0 @@ -package ru.progrm_jarvis.minecraft.playerutils.collection; - -import lombok.NonNull; -import lombok.Value; -import lombok.experimental.UtilityClass; -import org.bukkit.entity.Player; - -import java.util.Collection; -import java.util.Map; -import java.util.function.Function; - -@UtilityClass -public class PlayerContainers { - - public PlayerContainer wrap(@NonNull final Collection collectionOfPlayers) { - return new PlayerContainerCollectionWrapper(collectionOfPlayers); - } - - public PlayerContainer wrap(@NonNull final Map mapOfPlayers, - @NonNull final Function defaultValueSupplier) { - return new PlayerContainerMapWrapper<>(mapOfPlayers, defaultValueSupplier); - } - - @Value - protected class PlayerContainerCollectionWrapper implements PlayerContainer { - - @NonNull private final Collection collection; - - @Override - public void addPlayer(final Player player) { - collection.add(player); - } - - @Override - public void removePlayer(final Player player) { - collection.remove(player); - } - - @Override - public boolean containsPlayer(final Player player) { - return collection.contains(player); - } - - @Override - public Collection getPlayers() { - return collection; - } - } - - @Value - protected class PlayerContainerMapWrapper implements PlayerContainer { - - @NonNull private final Map map; - @NonNull private Function defaultValueSupplier; - - @Override - public void addPlayer(final Player player) { - map.put(player, defaultValueSupplier.apply(player)); - } - - @Override - public void removePlayer(final Player player) { - map.remove(player); - } - - @Override - public boolean containsPlayer(final Player player) { - return map.containsKey(player); - } - - @Override - public Collection getPlayers() { - return map.keySet(); - } - } -} diff --git a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/registry/PlayerRegistries.java b/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/registry/PlayerRegistries.java deleted file mode 100644 index 8c29d5731..000000000 --- a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/registry/PlayerRegistries.java +++ /dev/null @@ -1,47 +0,0 @@ -package ru.progrm_jarvis.minecraft.playerutils.registry; - -import com.google.common.base.MoreObjects; -import lombok.NonNull; -import lombok.experimental.UtilityClass; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import ru.progrm_jarvis.minecraft.playerutils.collection.PlayerContainer; -import ru.progrm_jarvis.minecraft.playerutils.collection.PlayerContainers; - -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; - -@UtilityClass -public class PlayerRegistries { - - private final Map DEFAULT_REGISTRIES = new ConcurrentHashMap<>(); - - private static final String DEFAULT_CHECK_INTERVAL_PROPERTY_NAME - = PlayerContainers.class.getTypeName().concat(".DEFAULT_CHECK_INTERVAL"); - - private static final long DEFAULT_CHECK_INTERVAL = Long.parseLong(MoreObjects.firstNonNull( - System.getProperty(DEFAULT_CHECK_INTERVAL_PROPERTY_NAME), "5") - ); - - public PlayerRegistry defaultRegistry(@NonNull final Plugin plugin) { - return DEFAULT_REGISTRIES.computeIfAbsent(plugin, pl -> new DefaultPlayerRegistry(pl, DEFAULT_CHECK_INTERVAL)); - } - - public C registerInDefaultRegistry(@NonNull final Plugin plugin, - @NonNull final C playerContainer) { - return defaultRegistry(plugin).register(playerContainer); - } - - public PlayerContainer registerInDefaultRegistry(@NonNull final Plugin plugin, - @NonNull final Collection collectionOfPlayers) { - return registerInDefaultRegistry(plugin, PlayerContainers.wrap(collectionOfPlayers)); - } - - public PlayerContainer registerInDefaultRegistry(@NonNull final Plugin plugin, - @NonNull final Map mapOfPlayers, - @NonNull final Function defaultValueSupplier) { - return registerInDefaultRegistry(plugin, PlayerContainers.wrap(mapOfPlayers, defaultValueSupplier)); - } -} diff --git a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/registry/PlayerRegistry.java b/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/registry/PlayerRegistry.java deleted file mode 100644 index 20b537440..000000000 --- a/player/src/main/java/ru/progrm_jarvis/minecraft/playerutils/registry/PlayerRegistry.java +++ /dev/null @@ -1,33 +0,0 @@ -package ru.progrm_jarvis.minecraft.playerutils.registry; - -import lombok.NonNull; -import lombok.val; -import org.bukkit.entity.Player; -import ru.progrm_jarvis.minecraft.playerutils.collection.PlayerContainer; -import ru.progrm_jarvis.minecraft.playerutils.collection.PlayerContainers; - -import java.util.Collection; -import java.util.Map; -import java.util.function.Function; - -public interface PlayerRegistry extends PlayerContainer { - - C register(C playerContainer); - - C unregister(C playerContainer); - - default PlayerContainer register(@NonNull final Collection playerCollection) { - val playerContainer = PlayerContainers.wrap(playerCollection); - register(playerContainer); - - return playerContainer; - } - - default PlayerContainer register(@NonNull final Map playerCollection, - @NonNull final Function defaultValueSupplier) { - val playerContainer = PlayerContainers.wrap(playerCollection, defaultValueSupplier); - register(playerContainer); - - return playerContainer; - } -} diff --git a/pom.xml b/pom.xml index 39b95b602..c3deea384 100644 --- a/pom.xml +++ b/pom.xml @@ -9,15 +9,13 @@ The version stands the same for all modules which will guarantee that modules depending on others will always be updated on update of others --> - 0.1.0-SNAPSHOT + 1.0.0-SNAPSHOT mc-unit - commons - nms - scheduler - player - config - fake-entity + minecraft-commons + ez-config + fake-entity-lib + lib-loader pom @@ -55,142 +53,85 @@ - UTF-8 1.8 1.8 - - ${maven.compiler.target} - ${maven.compiler.source} - ${maven.compiler.target} - 1.9.2 - - 5.3.2 - 1.3.2 + UTF-8 + + 1.0.0-SNAPSHOT + 1.0.0-SNAPSHOT + 5.8.2 + 1.9.0 + 4.4.0 - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - - - sonatype-snapshot-repo + sonatype-ossrh https://oss.sonatype.org/content/repositories/snapshots - - spigotmc-repo + minecraft-libraries + https://libraries.minecraft.net/ + + + spigotmc https://hub.spigotmc.org/nexus/content/groups/public/ - - dmulloy2-repo - http://repo.dmulloy2.net/nexus/repository/public/ + dmulloy2 + https://repo.dmulloy2.net/nexus/repository/public/ - clean verify - org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M2 - - 0 - true - - - - - org.codehaus.mojo - aspectj-maven-plugin - 1.11 - - - ${aspectj.compliance.version} - ${aspectj.source.version} - ${aspectj.target.version} - true - true - ignore - ${project.build.sourceEncoding} - - **/*.java - - true - - - - - default-compile - process-classes - - compile - - - - ${project.build.directory}/classes - - - - - default-testCompile - process-test-classes - - test-compile - - - - ${project.build.directory}/test-classes - - - - - - - org.aspectj - aspectjtools - ${aspectj.version} - - + 3.0.0-M5 - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - - ossrh - https://oss.sonatype.org/ - true - - + org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.0.1 org.apache.maven.plugins maven-source-plugin - 3.0.1 + 3.2.1 org.apache.maven.plugins maven-javadoc-plugin - 3.0.1 + 3.3.1 - none + + + apiNote + a + API note + + + implNote + a + Implementation note + + + implSpec + a + Implementation specification + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + @@ -199,93 +140,116 @@ - ${project.groupId} + ru.progrm-jarvis.minecraft mc-unit - ${project.version} + ${version.minecraft-utils} test - ${project.groupId} + ru.progrm-jarvis.minecraft minecraft-commons - ${project.version} - - - ${project.groupId} - nms-utils - ${project.version} + ${version.minecraft-utils} - ${project.groupId} - scheduler-utils - ${project.version} + ru.progrm-jarvis.minecraft + ez-cfg + ${version.minecraft-utils} - ${project.groupId} - player-utils - ${project.version} + ru.progrm-jarvis.minecraft + fake-entity-lib + ${version.minecraft-utils} - ${project.groupId} - ez-cfg - ${project.version} + ru.progrm-jarvis.minecraft + lib-loader + ${version.minecraft-utils} + + - ${project.groupId} - fake-entity-lib - ${project.version} + org.hamcrest + hamcrest-all + 1.3 + test + + ru.progrm-jarvis + java-commons + ${version.padla} + + + ru.progrm-jarvis + reflector + ${version.padla} + org.spigotmc spigot-api - 1.13.1-R0.1-SNAPSHOT + 1.16.1-R0.1-SNAPSHOT provided net.md-5 bungeecord-api - 1.13-SNAPSHOT + 1.16-R0.4 provided com.comphenix.protocol - ProtocolLib-API - 4.4.0 + ProtocolLib + 4.8.0 provided - com.comphenix.packetwrapper - PacketWrapper - 1.13-R0.1-SNAPSHOT + com.mojang + authlib + 1.5.21 - ru.progrm-jarvis.reflector - reflector - 1.2.2 + ru.progrm-jarvis.minecraft + packet-wrapper + 1.16.4-SNAPSHOT + + + commons-io + commons-io + 2.11.0 - net.sf.trove4j - trove4j - 3.0.3 + it.unimi.dsi + fastutil + 8.5.11 + + + org.apache.httpcomponents + httpclient + 4.5.13 org.projectlombok lombok - 1.18.4 + 1.18.22 provided + true - org.aspectj - aspectjrt - ${aspectj.version} + org.jetbrains + annotations + 23.0.0 + provided + true com.google.code.findbugs jsr305 3.0.2 + provided + true @@ -301,6 +265,12 @@ ${version.junit} test + + org.junit.jupiter + junit-jupiter-params + ${version.junit} + test + org.junit.platform junit-platform-launcher @@ -314,115 +284,126 @@ ${version.junit.platform} test - org.junit.platform junit-platform-surefire-provider - 1.2.0 + 1.3.2 org.mockito mockito-core - 2.23.0 + ${version.mockito} test org.mockito mockito-junit-jupiter - 2.23.0 + ${version.mockito} test + - ossrh-deploy - - - - org.sonatype.plugins - nexus-staging-maven-plugin - - - - - - sign + build-extras org.apache.maven.plugins - maven-gpg-plugin + maven-source-plugin - sign-artifacts - verify + attach-sources - sign + jar-no-fork - - - - - build-extras - - org.apache.maven.plugins - maven-source-plugin + maven-javadoc-plugin - attach-sources + attach-javadocs - jar-no-fork + jar + + + + + sign-artifacts + + org.apache.maven.plugins - maven-javadoc-plugin + maven-gpg-plugin - attach-javadocs + sign-artifacts + verify - jar + sign + + + --pinentry-mode + loopback + + + + + + sonatype-ossrh-deployment + + + + sonatype-ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + sonatype-ossrh + https://oss.sonatype.org/content/repositories/snapshots/ + + + - unit-test - - true - - - - - org.junit.jupiter - junit-jupiter-engine - - - org.junit.platform - junit-platform-launcher - - - - org.mockito - mockito-core - - - org.mockito - mockito-junit-jupiter - - + github-package-registry-deployment + + + github-package-registry + https://maven.pkg.github.com/JarvisCraft/minecraft-utils + + + + + automatic-central-release + + + + org.sonatype.plugins + nexus-staging-maven-plugin + true + + sonatype-ossrh + https://oss.sonatype.org/ + true + + + + diff --git a/project-version.sh b/project-version.sh deleted file mode 100644 index 45febce24..000000000 --- a/project-version.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -mvn -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec -q \ No newline at end of file diff --git a/scheduler/pom.xml b/scheduler/pom.xml deleted file mode 100644 index d3394f13c..000000000 --- a/scheduler/pom.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - 4.0.0 - - minecraft-utils - ru.progrm-jarvis.minecraft - 0.1.0-SNAPSHOT - - - scheduler-utils - - - - org.projectlombok - lombok - - - - org.spigotmc - spigot-api - - - net.sf.trove4j - trove4j - - - - - org.junit.jupiter - junit-jupiter-api - - - org.mockito - mockito-core - - - org.mockito - mockito-junit-jupiter - - - \ No newline at end of file diff --git a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/pool/KeyedLoopPool.java b/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/pool/KeyedLoopPool.java deleted file mode 100644 index 8afcb94ad..000000000 --- a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/pool/KeyedLoopPool.java +++ /dev/null @@ -1,28 +0,0 @@ -package ru.progrm_jarvis.minecraft.schedulerutils.pool; - -import lombok.NonNull; -import org.bukkit.plugin.Plugin; - -import java.util.Collection; - -/** - * A loop pool which allows storing its elements by keys. - * - * @param type of key for identifying tasks - * (may be unnecessary in some implementations so {@code null} may be used then) - */ -public interface KeyedLoopPool extends LoopPool { - - Plugin getPlugin(); - - void addTask(@NonNull final TaskOptions taskOptions, final K key, @NonNull final T task); - - @Override - default void addTask(final TaskOptions taskOptions, final T task) { - addTask(taskOptions, null, task); - } - - T removeTask(@NonNull final TaskOptions taskOptions, @NonNull final K key); - - Collection removeTasks(@NonNull final K key); -} diff --git a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/pool/LoopPool.java b/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/pool/LoopPool.java deleted file mode 100644 index 8d5233cb4..000000000 --- a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/pool/LoopPool.java +++ /dev/null @@ -1,55 +0,0 @@ -package ru.progrm_jarvis.minecraft.schedulerutils.pool; - -import com.google.common.base.Preconditions; -import gnu.trove.map.TLongObjectMap; -import gnu.trove.map.hash.TLongObjectHashMap; -import lombok.*; -import lombok.experimental.FieldDefaults; -import org.bukkit.plugin.Plugin; - -import java.util.Collection; - -/** - * A pool of schedulers which groups tasks with similar parameters - * (asynchrony and interval stored as {@link TaskOptions}}) into groups so that they are executed altogether. - * - * @param supported task type - */ -public interface LoopPool { - - Plugin getPlugin(); - - int tasksSize(); - - void addTask(@NonNull final TaskOptions taskOptions, @NonNull final T task); - - T removeTask(@NonNull final T task); - - Collection removeTasks(@NonNull final T task); - - Collection removeTasks(@NonNull final TaskOptions taskOptions); - - Collection clearTasks(); - - @Value - @AllArgsConstructor(access = AccessLevel.PROTECTED) - @FieldDefaults(level = AccessLevel.PRIVATE) - class TaskOptions { - - private static TLongObjectMap optionsPool = new TLongObjectHashMap<>(); - - boolean async; - long interval; - - public static TaskOptions of(final boolean async, long interval) { - Preconditions.checkArgument(interval > 0, "interval should be a positive number"); - - val key = async ? -interval : interval; - - var options = optionsPool.get(key); - if (options == null) optionsPool.put(key, options = new TaskOptions(async, interval)); - - return options; - } - } -} diff --git a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/pool/SingleWorkerLoopPool.java b/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/pool/SingleWorkerLoopPool.java deleted file mode 100644 index 2a6477c58..000000000 --- a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/pool/SingleWorkerLoopPool.java +++ /dev/null @@ -1,201 +0,0 @@ -package ru.progrm_jarvis.minecraft.schedulerutils.pool; - -import lombok.*; -import lombok.experimental.FieldDefaults; -import lombok.experimental.NonFinal; -import org.bukkit.plugin.Plugin; -import ru.progrm_jarvis.minecraft.schedulerutils.misc.KeyedSchedulerGroup; -import ru.progrm_jarvis.minecraft.schedulerutils.misc.SchedulerGroups; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -/** - * A loop pool which creates only one {@link org.bukkit.scheduler.BukkitTask} per sync-mode. - */ -@ToString -@EqualsAndHashCode -@RequiredArgsConstructor -@FieldDefaults(level = AccessLevel.PROTECTED) -public class SingleWorkerLoopPool implements KeyedLoopPool { - - boolean concurrent; - - @NonNull @Getter Plugin plugin; - KeyedSchedulerGroup, K> asyncWorker; - KeyedSchedulerGroup, K> syncWorker; - - protected static Collection mapToTasks(final Collection> tasks) { - return tasks.stream() - .map(task -> task.task) - .collect(Collectors.toList()); - } - - @Override - public int tasksSize() { - return (asyncWorker == null ? 0 : asyncWorker.size()) + (syncWorker == null ? 0 : syncWorker.size()); - } - - protected void initAsyncWorker() { - if (asyncWorker == null) asyncWorker = concurrent - ? SchedulerGroups.concurrentKeyedSchedulerGroup(plugin, true, 1, 1) - : SchedulerGroups.keyedSchedulerGroup(plugin, true, 1, 1); - } - - protected void initSyncWorker() { - if (syncWorker == null) syncWorker = concurrent - ? SchedulerGroups.concurrentKeyedSchedulerGroup(plugin, false, 1, 1) - : SchedulerGroups.keyedSchedulerGroup(plugin, false, 1, 1); - } - - @Override - public void addTask(final TaskOptions taskOptions, final T task) { - if (taskOptions.isAsync()) { - initAsyncWorker(); - - asyncWorker.addTask(new CountingTask<>(task, taskOptions.getInterval())); - } else { - initSyncWorker(); - - syncWorker.addTask(new CountingTask<>(task, taskOptions.getInterval())); - } - } - - @Override - public void addTask(final TaskOptions taskOptions, final K key, final T task) { - if (taskOptions.isAsync()) { - initAsyncWorker(); - - asyncWorker.addTask(key, new CountingTask<>(task, taskOptions.getInterval())); - } else { - initSyncWorker(); - - syncWorker.addTask(key, new CountingTask<>(task, taskOptions.getInterval())); - } - } - - @Override - public T removeTask(final T task) { - final Predicate> predicate = testedTask -> testedTask.task.equals(task); - - var removedTask = asyncWorker.removeTask(predicate); - if (removedTask == null) { - removedTask = syncWorker.removeTask(predicate); - if (removedTask == null) return null; - - checkAsync(); - - return removedTask.task; - } - - checkAsync(); - return removedTask.task; - } - - @Override - public Collection removeTasks(final T task) { - final Predicate> predicate = testedTask -> testedTask.task.equals(task); - var removedTasks = new ArrayList>(asyncWorker.removeTasks(predicate)); - checkAsync(); - removedTasks.addAll(syncWorker.removeTasks(predicate)); - checkSync(); - - return mapToTasks(removedTasks); - } - - @Override - public Collection removeTasks(final K key) { - val tasks = new ArrayList>(); - - var removedTasks = asyncWorker.removeTasks(key); - if (removedTasks != null) tasks.addAll(removedTasks); - checkAsync(); - - removedTasks = syncWorker.removeTasks(key); - if (removedTasks != null) tasks.addAll(removedTasks); - checkSync(); - - return mapToTasks(tasks); - } - - @Override - public T removeTask(final TaskOptions taskOptions, final K key) { - val async = taskOptions.isAsync(); - val tasks = (async ? asyncWorker.tasks() : syncWorker.tasks()).iterator(); - val interval = taskOptions.getInterval(); - - while (tasks.hasNext()) { - val task = tasks.next(); - if (task.interval == interval) { - if (async) checkAsync(); - else checkSync(); - - return task.task; - } - } - - return null; - } - - @Override - public Collection removeTasks(final TaskOptions taskOptions) { - val async = taskOptions.isAsync(); - val interval = taskOptions.getInterval(); - - // remove all tasks - val removedTasks = new ArrayList>(); - val tasks = (async ? asyncWorker : syncWorker).tasks().iterator(); - while (tasks.hasNext()) { - val task = tasks.next(); - if (task.getInterval() == interval) { - tasks.remove(); - removedTasks.add(task); - } - } - - if (async) checkAsync(); - else checkSync(); - - return mapToTasks(removedTasks); - } - - @Override - public Collection clearTasks() { - val tasks = asyncWorker.clearTasks(); - checkAsync(); - - tasks.addAll(syncWorker.clearTasks()); - checkSync(); - - return mapToTasks(tasks); - } - - protected void checkAsync() { - if (asyncWorker.isCancelled()) asyncWorker = null; - } - - protected void checkSync() { - if (syncWorker.size() == 0) syncWorker = null; - } - - @Value - @RequiredArgsConstructor - @FieldDefaults(level = AccessLevel.PRIVATE) - private static class CountingTask implements Runnable { - - final T task; - final long interval; - @NonFinal long counter; - - @Override - public void run() { - if (++counter == interval) { - counter = 0; - - task.run(); - } - } - } -} diff --git a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/task/initializer/BukkitTaskInitializer.java b/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/task/initializer/BukkitTaskInitializer.java deleted file mode 100644 index 926227403..000000000 --- a/scheduler/src/main/java/ru/progrm_jarvis/minecraft/schedulerutils/task/initializer/BukkitTaskInitializer.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.progrm_jarvis.minecraft.schedulerutils.task.initializer; - -import org.bukkit.scheduler.BukkitTask; - -@FunctionalInterface -public interface BukkitTaskInitializer { - - BukkitTask initialize(); - - default void shutdown() {} -} diff --git a/scheduler/src/test/java/ru/progrm_jarvis/minecraft/schedulerutils/pool/SingleWorkerLoopPoolTest.java b/scheduler/src/test/java/ru/progrm_jarvis/minecraft/schedulerutils/pool/SingleWorkerLoopPoolTest.java deleted file mode 100644 index 3287a4f24..000000000 --- a/scheduler/src/test/java/ru/progrm_jarvis/minecraft/schedulerutils/pool/SingleWorkerLoopPoolTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package ru.progrm_jarvis.minecraft.schedulerutils.pool; - -import org.bukkit.Bukkit; -import org.bukkit.Server; -import org.bukkit.plugin.Plugin; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Answers; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import ru.progrm_jarvis.minecraft.schedulerutils.pool.LoopPool.TaskOptions; - -import java.util.Random; - -import static java.lang.Math.abs; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; - -@ExtendWith(MockitoExtension.class) -class SingleWorkerLoopPoolTest { - - private final Random RANDOM = new Random(); - - @Mock private Plugin plugin; - @InjectMocks private SingleWorkerLoopPool loopPool; - - @BeforeAll - static void setUp() { - Bukkit.setServer(mock(Server.class, Answers.RETURNS_MOCKS)); - } - - /* TODO - @Test - void addTask() { - } - - @Test - void removeTasks() { - } - - @Test - void removeTasks() { - } - - @Test - void removeTask1() { - } - - @Test - void removeTasks1() { - } - */ - - @Test - void testClearTasks() { - final int syncTasks = 1 + RANDOM.nextInt(15), asyncTasks = 1 + RANDOM.nextInt(15); - for (int i = 0; i < syncTasks; i++) loopPool - .addTask(TaskOptions.of(false, 1 + abs(RANDOM.nextLong())), () -> {}); - for (int i = 0; i < asyncTasks; i++) loopPool - .addTask(TaskOptions.of(true, 1 + abs(RANDOM.nextLong())), () -> {}); - assertEquals(syncTasks + asyncTasks, loopPool.tasksSize()); - - loopPool.clearTasks(); - assertEquals(0, loopPool.tasksSize()); - } -}