diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
new file mode 100644
index 000000000..8fcf21cbc
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -0,0 +1,61 @@
+name: Report a bug
+description: You found a bug? Let us know!
+title: "[Bug]: "
+labels: [ "bug", "needs triage" ]
+projects: ["minestom/6"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thank you for reporting a bug to Minestom!
+ Please fill out the information below to help us understand the issue.
+ - type: markdown
+ attributes:
+ value: |
+ Before filling in the form fields, please consider the following:
+ - Ensure that you are using the latest version of Minestom.
+ - Search for existing issues in the [issue tracker](https://github.com/Minestom/Minestom/issues)
+ - type: textarea
+ attributes:
+ label: Affected Version
+ description: |
+ The unique version of the used dependency.
+ It can be found out via the Git class or in the dependency.
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Describe the bug
+ description: |
+ A clear and concise description of what the bug is.
+ If you have a screenshot of the bug, please attach it below.
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Steps to reproduce the bug
+ description: Tell us exactly how to reproduce the bug you are experiencing
+ placeholder: |
+ 1. ...
+ 2. ...
+ 3. ...
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Code sample
+ description: |
+ Please create a reproducible sample to show us the bug in action and attach it below between the lines with the backticks.
+ This helps us to verify that the bug is valid and prevents us from having to ask you for a sample later.
+
+ Without this we will be unlikely to be able to progress on the issue, so
+ we'll regretfully have to close it.
+
+ **Note**: Please do not upload screenshots of text. Instead, use code blocks
+ or the above mentioned ways to upload your code sample.
+ value: |
+ ```java
+ [Paste your code here]
+ ```
+ validations:
+ required: false
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..4c6c0a05c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,6 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Minestom Discord
+ url: https://discord.gg/minestom/
+ about: Please join our Discord server if you have any questions or concerns.
+ icon: https://github.com/simple-icons/simple-icons/blob/develop/icons/discord.svg
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml
new file mode 100644
index 000000000..5efef7995
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature.yml
@@ -0,0 +1,34 @@
+name: Feature Request
+description: Suggest an idea for this project
+title: "[Feature]: "
+labels: [ "enhancement" ]
+projects: ["minestom/6"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thank you for suggesting an idea to make Minestom better!
+ Please fill out the information below to help us understand your idea.
+ - type: textarea
+ attributes:
+ label: Is your feature request related to a problem?
+ description: Please give some context for this request. Why do you want it added?
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Describe the solution you'd like
+ description: Give us a clear description of what you want
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Describe alternatives you've considered
+ description: A clear and concise description of any alternative solutions or features you've considered.
+ validations:
+ required: false
+ - type: markdown
+ attributes:
+ value: |
+ Before submitting your feature request, please make sure you have done the following:
+ - [ ] Searched for existing feature requests
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/performance.yml b/.github/ISSUE_TEMPLATE/performance.yml
new file mode 100644
index 000000000..622d8ad02
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/performance.yml
@@ -0,0 +1,41 @@
+name: Performance Problem
+description: Report a performance problem which is related to Minestom
+labels: [ "needs triage", "performance" ]
+projects: ["minestom/6"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Before creating an issue regarding to the performance, please reach out for support on our [Discord](https://discord.gg/minestom/)
+ or in the [Discussions](https://github.com/Minestom/Minestom/discussions)!
+
+ **Please be aware: Performance issues can sometimes depend on your specific implementation and not on Minestom itself. If the situation is clear and it's not a problem with the project, we will close the issue without any comment.**
+ - type: input
+ attributes:
+ label: Used Minestom version
+ description: Which version of Minestom are you using?
+ placeholder: 1.0.0
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Describe the performance problem
+ description: If applicable, please describe your issue.
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Other
+ description: |
+ Please include other helpful links below.
+ The more information we receive, the quicker and more effective we can be at finding the solution to the issue.
+ validations:
+ required: false
+ - type: markdown
+ attributes:
+ value: |
+ Before submitting your issue, please make sure you have done the following:
+
+ 1. You are running the latest version of Minestom from [Release page](https://github.com/Minestom/Minestom)
+ 2. You searched for and ensured there isn't already an open issue regarding this
+ 3. Your version of Minecraft is supported by Minestom
\ No newline at end of file
diff --git a/.github/old-workflows/check-pr-style.yml b/.github/old-workflows/check-pr-style.yml
deleted file mode 100644
index 9e0568f90..000000000
--- a/.github/old-workflows/check-pr-style.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-name: Check PR code style
-
-on:
- pull_request:
- branches: [ master ]
-
-jobs:
- build:
-
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v2
- - name: Set up JDK 21
- uses: actions/setup-java@v1
- with:
- java-version: 21
- - name: Run java checkstyle
- uses: nikitasavinov/checkstyle-action@0.3.1
- with:
- # Report level for reviewdog [info,warning,error]
- level: info
- # Reporter of reviewdog command [github-pr-check,github-pr-review]
- reporter: github-pr-check
- # Filtering for the reviewdog command [added,diff_context,file,nofilter].
- filter_mode: added
- # Exit code for reviewdog when errors are found [true,false].
- fail_on_error: false
- # Checkstyle config file
- checkstyle_config: minestom_checks.xml
- checkstyle_version: "8.42"
diff --git a/.github/old-workflows/codeql-analysis.yml b/.github/old-workflows/codeql-analysis.yml
deleted file mode 100644
index 2325a1cc2..000000000
--- a/.github/old-workflows/codeql-analysis.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-#
-# ******** NOTE ********
-# We have attempted to detect the languages in your repository. Please check
-# the `language` matrix defined below to confirm you have the correct set of
-# supported CodeQL languages.
-#
-name: "CodeQL"
-
-on:
- push:
- branches: [ master ]
- pull_request:
- # The branches below must be a subset of the branches above
- branches: [ master ]
- schedule:
- - cron: '27 19 * * 3'
-
-jobs:
- analyze:
- name: Analyze
- runs-on: ubuntu-latest
-
- strategy:
- fail-fast: false
- matrix:
- language: [ 'java' ]
- # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
- # Learn more:
- # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v2
-
- # Initializes the CodeQL tools for scanning.
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v1
- with:
- languages: ${{ matrix.language }}
- # If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
- # Prefix the list here with "+" to use these queries and those in the config file.
- # queries: ./path/to/local/query, your-org/your-repo/queries@main
-
- # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
- # If this step fails, then you should remove it and run the build manually (see below)
- - name: Autobuild
- uses: github/codeql-action/autobuild@v1
-
- # âšī¸ Command-line programs to run using the OS shell.
- # đ https://git.io/JvXDl
-
- # âī¸ If the Autobuild fails above, remove it and uncomment the following three lines
- # and modify them (or add more) to build your code if your project
- # uses a compiled language
-
- #- run: |
- # make bootstrap
- # make release
-
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 000000000..c7d2916ba
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,29 @@
+## Proposed changes
+
+Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request.
+If it fixes a bug or resolves a feature request, be sure to link to that issue.
+
+## Types of changes
+
+What types of changes does your code introduce to this project?
+_Put an `x` in the boxes that apply_
+
+- [ ] Bugfix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] Documentation Update (if none of the other choices apply)
+
+## Checklist
+
+_Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of
+them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before
+merging your code._
+
+- [ ] I have read the [CONTRIBUTING.md](CONTRIBUTING.md)
+- [ ] I have added tests that prove my fix is effective or that my feature works
+- [ ] I have added necessary documentation (if appropriate)
+
+## Further comments
+
+If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you
+did and what alternatives you considered, etc...
\ No newline at end of file
diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml
new file mode 100644
index 000000000..d70d891b5
--- /dev/null
+++ b/.github/workflows/build-pr.yml
@@ -0,0 +1,43 @@
+name: Build PR
+on: [pull_request]
+jobs:
+ build_pr:
+ name: Build PR
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+ - name: Validate Gradle Wrapper
+ uses: gradle/wrapper-validation-action@v3
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: 21
+ - name: Cache Gradle packages
+ uses: actions/cache@v3
+ with:
+ path: ~/.gradle/caches
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
+ restore-keys: ${{ runner.os }}-gradle
+ - name: Build on ${{ matrix.os }}
+ run: ./gradlew test
+ publish:
+ name: Publish PR
+ if: contains(github.event.pull_request.labels.*.name, 'Publish Pull Request') && github.repository_owner == 'Minestom'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+ - id: vars
+ run: echo "short_commit_hash=${GITHUB_SHA::10}" >> $GITHUB_OUTPUT
+ - name: Publish Artifacts
+ env:
+ MINESTOM_VERSION: ${{ github.head_ref }}-${{ steps.vars.outputs.short_commit_hash }}
+ MINESTOM_CHANNEL: snapshot
+ run: |
+ ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository
+ echo "Version: ${SHORT_COMMIT_HASH}" >> $GITHUB_STEP_SUMMARY
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 000000000..0cea8b337
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,41 @@
+name: Build
+on:
+ push:
+ branches:
+ - master
+jobs:
+ Build:
+ name: Build and Publish
+ # Run on all label events (won't be duplicated) or all push events or on PR syncs not from the same repo
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+ - name: Validate Gradle Wrapper
+ uses: gradle/wrapper-validation-action@v3
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ cache: gradle
+ java-version: 21
+ - name: Cache Gradle packages
+ uses: actions/cache@v3
+ with:
+ path: ~/.gradle/caches
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
+ restore-keys: ${{ runner.os }}-gradle
+ - id: vars
+ run: echo "short_commit_hash=${GITHUB_SHA::10}" >> $GITHUB_OUTPUT
+ - name: Publish to Central via Build
+ if: github.repository_owner == 'Minestom'
+ run: |
+ ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository
+ echo "Version: ${SHORT_COMMIT_HASH}" >> $GITHUB_STEP_SUMMARY
+ env:
+ MINESTOM_VERSION: ${{ steps.vars.outputs.short_commit_hash }}
+ MINESTOM_CHANNEL: release
+ SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
+ SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
+ GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
+ GPG_PASSPHRASE: ${{ secrets.GPG_PASSWORD }}
\ No newline at end of file
diff --git a/.github/workflows/close_invalid_prs.yml b/.github/workflows/close_invalid_prs.yml
index 44c763b6d..bf9fa73d5 100644
--- a/.github/workflows/close_invalid_prs.yml
+++ b/.github/workflows/close_invalid_prs.yml
@@ -7,6 +7,7 @@ on:
jobs:
run:
+ name: Close invalid PRs
if: |
github.repository != github.event.pull_request.head.repo.full_name &&
(
diff --git a/.github/workflows/javadoc.yml b/.github/workflows/javadoc.yml
index 92d41fb98..0508616b1 100644
--- a/.github/workflows/javadoc.yml
+++ b/.github/workflows/javadoc.yml
@@ -6,9 +6,8 @@ on:
jobs:
build:
-
runs-on: ubuntu-latest
-
+ name: Build and deploy Javadoc
steps:
- uses: actions/checkout@v2
- name: Set up JDK 21
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
deleted file mode 100644
index 548cb3921..000000000
--- a/.github/workflows/pr.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-name: Build and test Minestom
-
-on:
- pull_request:
- branches: [ master ]
-
-jobs:
- tests:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - name: Set up JDK 21
- uses: actions/setup-java@v2
- with:
- distribution: 'zulu'
- java-version: 21
- - name: Grant execute permission for gradlew
- run: chmod +x gradlew
- - name: Setup gradle cache
- uses: burrunan/gradle-cache-action@v1
- with:
- save-generated-gradle-jars: false
- save-local-build-cache: false
- save-gradle-dependencies-cache: true
- save-maven-dependencies-cache: true
- # Ignore some of the paths when caching Maven Local repository
- maven-local-ignore-paths: |
- net/minestom/
- - name: Build Minestom
- run: ./gradlew classes testClasses javadoc
- - name: Run Minestom tests
- run: ./gradlew test
- publish:
- runs-on: ubuntu-latest
- if: github.event.pull_request.head.repo.full_name == github.repository
- env:
- SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
- SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
- GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
- GPG_PASSPHRASE: ${{ secrets.GPG_PASSWORD }}
- steps:
- - uses: actions/checkout@v2
- - name: Set up JDK 21
- uses: actions/setup-java@v2
- with:
- java-version: '21'
- distribution: 'temurin'
- - name: Set outputs
- id: vars
- run: |
- export ACTUAL=${{ github.event.pull_request.head.sha }}
- echo "short_commit_hash=${ACTUAL::10}" >> $GITHUB_OUTPUT
- - name: Publish to Sonatype
- env:
- MINESTOM_VERSION: ${{ github.head_ref }}-${{ steps.vars.outputs.short_commit_hash }}
- MINESTOM_CHANNEL: snapshot
- run: |
- ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository
- echo "Version: ${SHORT_COMMIT_HASH}" >> $GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/snapshot-deploy.yaml b/.github/workflows/snapshot-deploy.yaml
deleted file mode 100644
index cf032b8da..000000000
--- a/.github/workflows/snapshot-deploy.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-name: Gradle Publish to Maven Central
-
-on:
- push:
- branches: [ master ]
-
-jobs:
- build:
- runs-on: ubuntu-latest
- env:
- SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
- SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
- GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
- GPG_PASSPHRASE: ${{ secrets.GPG_PASSWORD }}
- steps:
- - uses: actions/checkout@v2
- - name: Set up JDK 21
- uses: actions/setup-java@v2
- with:
- java-version: '21'
- distribution: 'temurin'
- - name: Set outputs
- id: vars
- run: |
- echo "short_commit_hash=${GITHUB_SHA::10}" >> $GITHUB_OUTPUT
- - name: Publish to Sonatype
- env:
- MINESTOM_VERSION: ${{ steps.vars.outputs.short_commit_hash }}
- MINESTOM_CHANNEL: release
- run: |
- ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository
- echo "Version: ${SHORT_COMMIT_HASH}" >> $GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/trigger-jitpack-build.yml b/.github/workflows/trigger-jitpack-build.yml
deleted file mode 100644
index 8366c4f6f..000000000
--- a/.github/workflows/trigger-jitpack-build.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-name: Trigger Jitpack Build
-on:
- push:
- branches: [ master ]
-
- workflow_dispatch:
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: Trigger Jitpack Build
- run: curl "https://jitpack.io/com/github/Minestom/Minestom/${GITHUB_SHA:0:10}/build.log"
diff --git a/code-generators/src/main/java/net/minestom/codegen/Generators.java b/code-generators/src/main/java/net/minestom/codegen/Generators.java
index 3ac9a26e5..d251b5dec 100644
--- a/code-generators/src/main/java/net/minestom/codegen/Generators.java
+++ b/code-generators/src/main/java/net/minestom/codegen/Generators.java
@@ -52,6 +52,7 @@ public class Generators {
generator.generate(resource("attributes.json"), "net.minestom.server.entity.attribute", "Attribute", "AttributeImpl", "Attributes");
generator.generate(resource("feature_flags.json"), "net.minestom.server", "FeatureFlag", "FeatureFlagImpl", "FeatureFlags");
generator.generate(resource("villager_professions.json"), "net.minestom.server.entity", "VillagerProfession", "VillagerProfessionImpl", "VillagerProfessions");
+ generator.generate(resource("game_events.json"), "net.minestom.server.game", "GameEvent", "GameEventImpl", "GameEvents");
// Dynamic registries
diff --git a/demo/src/main/java/net/minestom/demo/PlayerInit.java b/demo/src/main/java/net/minestom/demo/PlayerInit.java
index 6fd80f58d..71e1623c4 100644
--- a/demo/src/main/java/net/minestom/demo/PlayerInit.java
+++ b/demo/src/main/java/net/minestom/demo/PlayerInit.java
@@ -141,7 +141,6 @@ public class PlayerInit {
inventory.addItemStack(getFoodItem(10000));
inventory.addItemStack(getFoodItem(Integer.MAX_VALUE));
-
if (event.isFirstSpawn()) {
event.getPlayer().sendNotification(new Notification(
Component.text("Welcome!"),
diff --git a/src/autogenerated/java/net/minestom/server/game/GameEvents.java b/src/autogenerated/java/net/minestom/server/game/GameEvents.java
new file mode 100644
index 000000000..44e09b862
--- /dev/null
+++ b/src/autogenerated/java/net/minestom/server/game/GameEvents.java
@@ -0,0 +1,127 @@
+package net.minestom.server.game;
+
+/**
+ * Code autogenerated, do not edit!
+ */
+@SuppressWarnings("unused")
+interface GameEvents {
+ GameEvent BLOCK_ACTIVATE = GameEventImpl.get("minecraft:block_activate");
+
+ GameEvent BLOCK_ATTACH = GameEventImpl.get("minecraft:block_attach");
+
+ GameEvent BLOCK_CHANGE = GameEventImpl.get("minecraft:block_change");
+
+ GameEvent BLOCK_CLOSE = GameEventImpl.get("minecraft:block_close");
+
+ GameEvent BLOCK_DEACTIVATE = GameEventImpl.get("minecraft:block_deactivate");
+
+ GameEvent BLOCK_DESTROY = GameEventImpl.get("minecraft:block_destroy");
+
+ GameEvent BLOCK_DETACH = GameEventImpl.get("minecraft:block_detach");
+
+ GameEvent BLOCK_OPEN = GameEventImpl.get("minecraft:block_open");
+
+ GameEvent BLOCK_PLACE = GameEventImpl.get("minecraft:block_place");
+
+ GameEvent CONTAINER_CLOSE = GameEventImpl.get("minecraft:container_close");
+
+ GameEvent CONTAINER_OPEN = GameEventImpl.get("minecraft:container_open");
+
+ GameEvent DRINK = GameEventImpl.get("minecraft:drink");
+
+ GameEvent EAT = GameEventImpl.get("minecraft:eat");
+
+ GameEvent ELYTRA_GLIDE = GameEventImpl.get("minecraft:elytra_glide");
+
+ GameEvent ENTITY_DAMAGE = GameEventImpl.get("minecraft:entity_damage");
+
+ GameEvent ENTITY_DIE = GameEventImpl.get("minecraft:entity_die");
+
+ GameEvent ENTITY_DISMOUNT = GameEventImpl.get("minecraft:entity_dismount");
+
+ GameEvent ENTITY_INTERACT = GameEventImpl.get("minecraft:entity_interact");
+
+ GameEvent ENTITY_MOUNT = GameEventImpl.get("minecraft:entity_mount");
+
+ GameEvent ENTITY_PLACE = GameEventImpl.get("minecraft:entity_place");
+
+ GameEvent ENTITY_ACTION = GameEventImpl.get("minecraft:entity_action");
+
+ GameEvent EQUIP = GameEventImpl.get("minecraft:equip");
+
+ GameEvent EXPLODE = GameEventImpl.get("minecraft:explode");
+
+ GameEvent FLAP = GameEventImpl.get("minecraft:flap");
+
+ GameEvent FLUID_PICKUP = GameEventImpl.get("minecraft:fluid_pickup");
+
+ GameEvent FLUID_PLACE = GameEventImpl.get("minecraft:fluid_place");
+
+ GameEvent HIT_GROUND = GameEventImpl.get("minecraft:hit_ground");
+
+ GameEvent INSTRUMENT_PLAY = GameEventImpl.get("minecraft:instrument_play");
+
+ GameEvent ITEM_INTERACT_FINISH = GameEventImpl.get("minecraft:item_interact_finish");
+
+ GameEvent ITEM_INTERACT_START = GameEventImpl.get("minecraft:item_interact_start");
+
+ GameEvent JUKEBOX_PLAY = GameEventImpl.get("minecraft:jukebox_play");
+
+ GameEvent JUKEBOX_STOP_PLAY = GameEventImpl.get("minecraft:jukebox_stop_play");
+
+ GameEvent LIGHTNING_STRIKE = GameEventImpl.get("minecraft:lightning_strike");
+
+ GameEvent NOTE_BLOCK_PLAY = GameEventImpl.get("minecraft:note_block_play");
+
+ GameEvent PRIME_FUSE = GameEventImpl.get("minecraft:prime_fuse");
+
+ GameEvent PROJECTILE_LAND = GameEventImpl.get("minecraft:projectile_land");
+
+ GameEvent PROJECTILE_SHOOT = GameEventImpl.get("minecraft:projectile_shoot");
+
+ GameEvent SCULK_SENSOR_TENDRILS_CLICKING = GameEventImpl.get("minecraft:sculk_sensor_tendrils_clicking");
+
+ GameEvent SHEAR = GameEventImpl.get("minecraft:shear");
+
+ GameEvent SHRIEK = GameEventImpl.get("minecraft:shriek");
+
+ GameEvent SPLASH = GameEventImpl.get("minecraft:splash");
+
+ GameEvent STEP = GameEventImpl.get("minecraft:step");
+
+ GameEvent SWIM = GameEventImpl.get("minecraft:swim");
+
+ GameEvent TELEPORT = GameEventImpl.get("minecraft:teleport");
+
+ GameEvent UNEQUIP = GameEventImpl.get("minecraft:unequip");
+
+ GameEvent RESONATE_1 = GameEventImpl.get("minecraft:resonate_1");
+
+ GameEvent RESONATE_2 = GameEventImpl.get("minecraft:resonate_2");
+
+ GameEvent RESONATE_3 = GameEventImpl.get("minecraft:resonate_3");
+
+ GameEvent RESONATE_4 = GameEventImpl.get("minecraft:resonate_4");
+
+ GameEvent RESONATE_5 = GameEventImpl.get("minecraft:resonate_5");
+
+ GameEvent RESONATE_6 = GameEventImpl.get("minecraft:resonate_6");
+
+ GameEvent RESONATE_7 = GameEventImpl.get("minecraft:resonate_7");
+
+ GameEvent RESONATE_8 = GameEventImpl.get("minecraft:resonate_8");
+
+ GameEvent RESONATE_9 = GameEventImpl.get("minecraft:resonate_9");
+
+ GameEvent RESONATE_10 = GameEventImpl.get("minecraft:resonate_10");
+
+ GameEvent RESONATE_11 = GameEventImpl.get("minecraft:resonate_11");
+
+ GameEvent RESONATE_12 = GameEventImpl.get("minecraft:resonate_12");
+
+ GameEvent RESONATE_13 = GameEventImpl.get("minecraft:resonate_13");
+
+ GameEvent RESONATE_14 = GameEventImpl.get("minecraft:resonate_14");
+
+ GameEvent RESONATE_15 = GameEventImpl.get("minecraft:resonate_15");
+}
diff --git a/src/main/java/net/minestom/server/ServerFlag.java b/src/main/java/net/minestom/server/ServerFlag.java
index 5dcd1dcb2..653c0e09c 100644
--- a/src/main/java/net/minestom/server/ServerFlag.java
+++ b/src/main/java/net/minestom/server/ServerFlag.java
@@ -27,6 +27,7 @@ public final class ServerFlag {
public static final int PLAYER_PACKET_QUEUE_SIZE = intProperty("minestom.packet-queue-size", 1000);
public static final long KEEP_ALIVE_DELAY = longProperty("minestom.keep-alive-delay", 10_000);
public static final long KEEP_ALIVE_KICK = longProperty("minestom.keep-alive-kick", 15_000);
+ public static final int PLAYER_CHUNK_UPDATE_LIMITER_HISTORY_SIZE = intProperty("minestom.player.chunk-update-limiter-history-size", 5, 0, Integer.MAX_VALUE);
// Network buffers
public static final int MAX_PACKET_SIZE = intProperty("minestom.max-packet-size", 2_097_151); // 3 bytes var-int
@@ -98,8 +99,19 @@ public final class ServerFlag {
return System.getProperty(name);
}
+ private static int intProperty(String name, int defaultValue, int minValue, int maxValue) {
+ int value = Integer.getInteger(name, defaultValue);
+ if (value < minValue || value > maxValue) {
+ throw new IllegalArgumentException(String.format(
+ "Property '%s' value must be in range [%d..%d] but was %d",
+ name, minValue, maxValue, value
+ ));
+ }
+ return value;
+ }
+
private static int intProperty(String name, int defaultValue) {
- return Integer.getInteger(name, defaultValue);
+ return intProperty(name, defaultValue, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
private static long longProperty(String name, long defaultValue) {
diff --git a/src/main/java/net/minestom/server/collision/BoundingBox.java b/src/main/java/net/minestom/server/collision/BoundingBox.java
index b68383401..27a408143 100644
--- a/src/main/java/net/minestom/server/collision/BoundingBox.java
+++ b/src/main/java/net/minestom/server/collision/BoundingBox.java
@@ -5,6 +5,7 @@ import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.EntityPose;
import net.minestom.server.instance.block.BlockFace;
+import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -21,11 +22,11 @@ public record BoundingBox(Vec relativeStart, Vec relativeEnd) implements Shape {
final static BoundingBox ZERO = new BoundingBox(Vec.ZERO, Vec.ZERO);
public BoundingBox(double width, double height, double depth, Point offset) {
- this(new Vec(-width / 2.0, 0.0, -depth / 2.0).add(offset), new Vec(width / 2.0, height, depth / 2.0).add(offset));
+ this(Vec.fromPoint(offset), new Vec(width, height, depth).add(offset));
}
public BoundingBox(double width, double height, double depth) {
- this(width, height, depth, Vec.ZERO);
+ this(width, height, depth, new Vec(-width / 2, 0, -depth / 2));
}
@Override
@@ -94,6 +95,41 @@ public record BoundingBox(Vec relativeStart, Vec relativeEnd) implements Shape {
return new BoundingBox(width(), height(), depth(), offset);
}
+ /**
+ * Creates a new {@link BoundingBox} with an expanded size from its center in every plane.
+ *
+ * Equivalent to an expansion and an offset where the point is the three-axis offset.
+ * Particularly useful when you already use centered and aligned minY=0 position.
+ *
+ * @param x the X offset, this will be applied on both sides
+ * @param y the Y offset, this will be applied on both sides
+ * @param z the Z offset, this will be applied on both sides
+ * @return a new {@link BoundingBox} expanded and centered from the original minY
+ */
+ @Contract(pure = true)
+ public @NotNull BoundingBox grow(double x, double y, double z) {
+ final double newWidth = width() + x, newDepth = depth() + z;
+ final Vec centerOffset = new Vec(-newWidth / 2, minY() - y / 2, -newDepth / 2);
+ return new BoundingBox(newWidth, height() + y, newDepth, centerOffset);
+ }
+
+ /**
+ * Creates a new {@link BoundingBox} with an expanded size from its center in every plane.
+ *
+ * Equivalent to a double expansion and an offset where the point is the three-axis offset.
+ * Particularly useful when you already use centered and aligned minY=0 position.
+ *
+ * @param x the X offset, this will be applied on both sides
+ * @param y the Y offset, this will be applied on both sides
+ * @param z the Z offset, this will be applied on both sides
+ * @return a new {@link BoundingBox} expanded and centered from the original minY
+ */
+ @Contract(pure = true)
+ public @NotNull BoundingBox growSymmetrically(double x, double y, double z) {
+ // Double all amounts to make it symmetric conformance to xyz
+ return grow(x * 2, y * 2, z * 2);
+ }
+
public double width() {
return relativeEnd.x() - relativeStart.x();
}
diff --git a/src/main/java/net/minestom/server/command/CommandParserImpl.java b/src/main/java/net/minestom/server/command/CommandParserImpl.java
index 07c3125f4..7f93b2115 100644
--- a/src/main/java/net/minestom/server/command/CommandParserImpl.java
+++ b/src/main/java/net/minestom/server/command/CommandParserImpl.java
@@ -132,8 +132,8 @@ final class CommandParserImpl implements CommandParser {
int start = reader.cursor();
if (reader.hasRemaining()) {
- ArgumentResult> result = parseArgument(sender, argument, reader);
SuggestionCallback suggestionCallback = argument.getSuggestionCallback();
+ ArgumentResult> result = parseArgument(sender, argument, reader);
NodeResult nodeResult = new NodeResult(node, chain, (ArgumentResult) result, suggestionCallback);
chain.append(nodeResult);
if (suggestionCallback != null) chain.suggestionCallback = suggestionCallback;
@@ -200,12 +200,27 @@ final class CommandParserImpl implements CommandParser {
if (reader.hasRemaining()) {
// Trailing data is a syntax error
- return new NodeResult(
- node,
+ // Can get to here if there's a default executor even if the user is still typing the command
+ // So let's supply the next argument's suggestion callback if it exists
+ Node returnNode = node;
+ SuggestionCallback suggestionCallback = argument.getSuggestionCallback();
+ List nextNodes = node.next();
+ if (!nextNodes.isEmpty()) {
+ returnNode = nextNodes.getFirst();
+ suggestionCallback = returnNode.argument().getSuggestionCallback();
+ }
+ NodeResult nodeResult = new NodeResult(
+ returnNode,
chain,
new ArgumentResult.SyntaxError<>("Command has trailing data", "", -1),
- argument.getSuggestionCallback()
+ suggestionCallback
);
+ chain.suggestionCallback = suggestionCallback;
+ // prevent duplicates from being added (Fixes CommandParseTest#singleCommandWithMultipleSyntax() failure)
+ if (chain.getArgs().stream().noneMatch(arg -> arg.getId().equals(argument.getId()))) {
+ chain.append(nodeResult);
+ }
+ return nodeResult;
}
// Command was successful!
diff --git a/src/main/java/net/minestom/server/coordinate/BlockVec.java b/src/main/java/net/minestom/server/coordinate/BlockVec.java
index f8df8214b..e8be63043 100644
--- a/src/main/java/net/minestom/server/coordinate/BlockVec.java
+++ b/src/main/java/net/minestom/server/coordinate/BlockVec.java
@@ -28,6 +28,21 @@ public record BlockVec(double x, double y, double z) implements Point {
this(point.x(), point.y(), point.z());
}
+ @Override
+ public int blockX() {
+ return (int) x;
+ }
+
+ @Override
+ public int blockY() {
+ return (int) y;
+ }
+
+ @Override
+ public int blockZ() {
+ return (int) z;
+ }
+
@Override
public @NotNull Point withX(@NotNull DoubleUnaryOperator operator) {
return new Vec(operator.applyAsDouble(x), y, z);
diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java
index 665562783..0dbf8fcaa 100644
--- a/src/main/java/net/minestom/server/entity/Entity.java
+++ b/src/main/java/net/minestom/server/entity/Entity.java
@@ -46,6 +46,7 @@ import net.minestom.server.timer.Schedulable;
import net.minestom.server.timer.Scheduler;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.utils.ArrayUtils;
+import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.PacketViewableUtils;
import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.block.BlockIterator;
@@ -79,6 +80,10 @@ import java.util.function.UnaryOperator;
*/
public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, EventHandler, Taggable,
HoverEventSource, Sound.Emitter, Shape, AcquirableSource {
+ // This is somewhat arbitrary, but we don't want to hit the max int ever because it is very easy to
+ // overflow while working with a position at the max int (for example, looping over a bounding box)
+ private static final int MAX_COORDINATE = 2_000_000_000;
+
private static final AtomicInteger LAST_ENTITY_ID = new AtomicInteger();
// Certain entities should only have their position packets sent during synchronization
@@ -96,7 +101,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
protected Instance instance;
protected Chunk currentChunk;
- protected Pos position;
+ protected Pos position; // Should be updated by setPositionInternal only.
protected Pos previousPosition;
protected Pos lastSyncedPosition;
protected boolean onGround;
@@ -202,6 +207,19 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
this(entityType, UUID.randomUUID());
}
+ protected void setPositionInternal(@NotNull Pos newPosition) {
+ if (newPosition.x() >= MAX_COORDINATE || newPosition.x() <= -MAX_COORDINATE ||
+ newPosition.y() >= MAX_COORDINATE || newPosition.y() <= -MAX_COORDINATE ||
+ newPosition.z() >= MAX_COORDINATE || newPosition.z() <= -MAX_COORDINATE) {
+ newPosition = newPosition.withCoord(
+ MathUtils.clamp(newPosition.x(), -MAX_COORDINATE, MAX_COORDINATE),
+ MathUtils.clamp(newPosition.y(), -MAX_COORDINATE, MAX_COORDINATE),
+ MathUtils.clamp(newPosition.z(), -MAX_COORDINATE, MAX_COORDINATE)
+ );
+ }
+ this.position = newPosition;
+ }
+
/**
* Schedules a task to be run during the next entity tick.
*
@@ -325,7 +343,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
final Runnable endCallback = () -> {
this.previousPosition = this.position;
- this.position = globalPosition;
+ setPositionInternal(globalPosition);
this.velocity = globalVelocity;
refreshCoordinate(globalPosition);
if (this instanceof Player player) player.synchronizePositionAfterTeleport(position, velocity, flags, shouldConfirm);
@@ -356,7 +374,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
public void setView(float yaw, float pitch) {
final Pos currentPosition = this.position;
if (currentPosition.sameView(yaw, pitch)) return;
- this.position = currentPosition.withView(yaw, pitch);
+ setPositionInternal(currentPosition.withView(yaw, pitch));
synchronizeView();
}
@@ -784,7 +802,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
if (previousInstance != null) removeFromInstance(previousInstance);
this.isActive = true;
- this.position = spawnPosition;
+ setPositionInternal(spawnPosition);
this.previousPosition = spawnPosition;
this.lastSyncedPosition = spawnPosition;
this.previousPhysicsResult = null;
@@ -1236,7 +1254,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
final var previousPosition = this.position;
final Pos position = ignoreView ? previousPosition.withCoord(newPosition) : newPosition;
if (position.equals(lastSyncedPosition)) return;
- this.position = position;
+ setPositionInternal(position);
this.previousPosition = previousPosition;
if (!position.samePoint(previousPosition)) refreshCoordinate(position);
if (nextSynchronizationTick <= ticks + 1 || !sendPackets) {
@@ -1297,7 +1315,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
final Pos newPassengerPos = oldPassengerPos.withCoord(newPosition.x(),
newPosition.y() + EntityUtils.getPassengerHeightOffset(this, passenger),
newPosition.z());
- passenger.position = newPassengerPos;
+ passenger.setPositionInternal(newPassengerPos);
passenger.previousPosition = oldPassengerPos;
passenger.refreshCoordinate(newPassengerPos);
}
@@ -1474,7 +1492,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
this.removed = true;
if (!permanent) {
// Reset some state to be ready for re-use
- this.position = Pos.ZERO;
+ setPositionInternal(Pos.ZERO);
this.previousPosition = Pos.ZERO;
this.lastSyncedPosition = Pos.ZERO;
}
diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java
index eb2f9d3ed..c23c1ea8f 100644
--- a/src/main/java/net/minestom/server/entity/Player.java
+++ b/src/main/java/net/minestom/server/entity/Player.java
@@ -191,7 +191,7 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
// Game state (https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol#Game_Event)
private boolean enableRespawnScreen;
- private final ChunkUpdateLimitChecker chunkUpdateLimitChecker = new ChunkUpdateLimitChecker(6);
+ private final ChunkUpdateLimitChecker chunkUpdateLimitChecker = new ChunkUpdateLimitChecker(ServerFlag.PLAYER_CHUNK_UPDATE_LIMITER_HISTORY_SIZE);
// Experience orb pickup
protected Cooldown experiencePickupCooldown = new Cooldown(Duration.of(10, TimeUnit.SERVER_TICK));
diff --git a/src/main/java/net/minestom/server/entity/metadata/animal/tameable/WolfMeta.java b/src/main/java/net/minestom/server/entity/metadata/animal/tameable/WolfMeta.java
index f6f91f205..3934bfe44 100644
--- a/src/main/java/net/minestom/server/entity/metadata/animal/tameable/WolfMeta.java
+++ b/src/main/java/net/minestom/server/entity/metadata/animal/tameable/WolfMeta.java
@@ -58,7 +58,7 @@ public class WolfMeta extends TameableAnimalMeta {
}
public sealed interface Variant extends ProtocolObject, WolfVariants permits VariantImpl {
- @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::wolfVariant);
+ @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::wolfVariant, true);
@NotNull BinaryTagSerializer> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::wolfVariant);
static @NotNull Variant create(
diff --git a/src/main/java/net/minestom/server/entity/metadata/other/PaintingMeta.java b/src/main/java/net/minestom/server/entity/metadata/other/PaintingMeta.java
index a26b84ffc..1d47aae41 100644
--- a/src/main/java/net/minestom/server/entity/metadata/other/PaintingMeta.java
+++ b/src/main/java/net/minestom/server/entity/metadata/other/PaintingMeta.java
@@ -79,7 +79,7 @@ public class PaintingMeta extends EntityMeta implements ObjectDataProvider {
}
public sealed interface Variant extends ProtocolObject, PaintingVariants permits VariantImpl {
- @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::paintingVariant);
+ @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::paintingVariant, true);
@NotNull BinaryTagSerializer> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::paintingVariant);
static @NotNull Variant create(
diff --git a/src/main/java/net/minestom/server/entity/pathfinding/PNode.java b/src/main/java/net/minestom/server/entity/pathfinding/PNode.java
index 919a274ad..606928931 100644
--- a/src/main/java/net/minestom/server/entity/pathfinding/PNode.java
+++ b/src/main/java/net/minestom/server/entity/pathfinding/PNode.java
@@ -95,7 +95,7 @@ public class PNode {
}
@ApiStatus.Internal
- @NotNull Type getType() {
+ public @NotNull Type getType() {
return type;
}
diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java
index e6710669f..c7af4fec8 100644
--- a/src/main/java/net/minestom/server/event/EventNodeImpl.java
+++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java
@@ -292,10 +292,21 @@ non-sealed class EventNodeImpl implements EventNode {
aClass -> new Handle<>((Class) aClass));
handle.invalidate();
});
+ invalidateRecursiveSuperclasses(eventClass);
final EventNodeImpl super T> parent = this.parent;
if (parent != null) parent.invalidateEvent(eventClass);
}
+ private void invalidateRecursiveSuperclasses(@NotNull Class> eventClass) {
+ if (RecursiveEvent.class.isAssignableFrom(eventClass)) {
+ for (var cls : this.handleMap.keySet()) {
+ if (eventClass.isAssignableFrom(cls)) {
+ this.handleMap.get(cls).invalidate();
+ }
+ }
+ }
+ }
+
private ListenerEntry getEntry(Class extends T> type) {
return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>());
}
diff --git a/src/main/java/net/minestom/server/game/GameEvent.java b/src/main/java/net/minestom/server/game/GameEvent.java
new file mode 100644
index 000000000..a2853834a
--- /dev/null
+++ b/src/main/java/net/minestom/server/game/GameEvent.java
@@ -0,0 +1,66 @@
+package net.minestom.server.game;
+
+
+import net.minestom.server.registry.Registry;
+import net.minestom.server.registry.StaticProtocolObject;
+import net.minestom.server.utils.NamespaceID;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+
+/**
+ * Represents a game event.
+ * Used for a wide variety of events, from weather to bed use to game mode to demo messages.
+ */
+public sealed interface GameEvent extends StaticProtocolObject permits GameEventImpl {
+
+ /**
+ * Returns the game event registry.
+ *
+ * @return the game event registry or null if not found
+ */
+ @Contract(pure = true)
+ @Nullable
+ Registry.GameEventEntry registry();
+
+ /**
+ * Gets the namespace ID of this game event.
+ *
+ * @return the namespace ID
+ */
+ @Override
+ @NotNull
+ NamespaceID namespace();
+
+ /**
+ * Gets the game events from the registry.
+ *
+ * @return the game events
+ */
+ static @NotNull Collection<@NotNull GameEvent> values() {
+ return GameEventImpl.values();
+ }
+
+ /**
+ * Gets a game event by its namespace ID.
+ *
+ * @param namespaceID the namespace ID
+ * @return the game event or null if not found
+ */
+ static @Nullable GameEvent fromNamespaceId(@NotNull String namespaceID) {
+ return GameEventImpl.getSafe(namespaceID);
+ }
+
+ /**
+ * Gets a game event by its namespace ID.
+ *
+ * @param namespaceID the namespace ID
+ * @return the game event or null if not found
+ */
+ static @Nullable GameEvent fromNamespaceId(@NotNull NamespaceID namespaceID) {
+ return fromNamespaceId(namespaceID.asString());
+ }
+
+}
diff --git a/src/main/java/net/minestom/server/game/GameEventImpl.java b/src/main/java/net/minestom/server/game/GameEventImpl.java
new file mode 100644
index 000000000..d07ab20d0
--- /dev/null
+++ b/src/main/java/net/minestom/server/game/GameEventImpl.java
@@ -0,0 +1,65 @@
+package net.minestom.server.game;
+
+
+import net.minestom.server.registry.Registry;
+import net.minestom.server.utils.NamespaceID;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+/**
+ * Represents a game event implementation.
+ * Used for a wide variety of events, from weather to bed use to game mode to demo messages.
+ */
+record GameEventImpl(Registry.GameEventEntry registry, NamespaceID namespace, int id) implements GameEvent {
+ private static final Registry.Container CONTAINER = Registry.createStaticContainer(Registry.Resource.GAME_EVENTS, GameEventImpl::createImpl);
+
+ /**
+ * Creates a new {@link GameEventImpl} with the given namespace and properties.
+ *
+ * @param namespace the namespace
+ * @param properties the properties
+ * @return a new {@link GameEventImpl}
+ */
+ private static GameEventImpl createImpl(String namespace, Registry.Properties properties) {
+ return new GameEventImpl(Registry.gameEventEntry(namespace, properties));
+ }
+
+ /**
+ * Creates a new {@link GameEventImpl} with the given registry.
+ *
+ * @param registry the registry
+ */
+ private GameEventImpl(Registry.GameEventEntry registry) {
+ this(registry, registry.namespace(), registry.main().getInt("id"));
+ }
+
+ /**
+ * Gets the game events from the registry.
+ *
+ * @return the game events
+ */
+ static Collection values() {
+ return CONTAINER.values();
+ }
+
+ /**
+ * Gets a game event by its namespace ID.
+ *
+ * @param namespace the namespace ID
+ * @return the game event or null if not found
+ */
+ public static GameEvent get(@NotNull String namespace) {
+ return CONTAINER.get(namespace);
+ }
+
+ /**
+ * Gets a game event by its namespace ID.
+ *
+ * @param namespace the namespace ID
+ * @return the game event or null if not found
+ */
+ static GameEvent getSafe(@NotNull String namespace) {
+ return CONTAINER.getSafe(namespace);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/minestom/server/gamedata/tags/Tag.java b/src/main/java/net/minestom/server/gamedata/tags/Tag.java
index a6c321232..7783fef32 100644
--- a/src/main/java/net/minestom/server/gamedata/tags/Tag.java
+++ b/src/main/java/net/minestom/server/gamedata/tags/Tag.java
@@ -2,8 +2,8 @@ package net.minestom.server.gamedata.tags;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
-import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.EntityType;
+import net.minestom.server.game.GameEvent;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.Material;
import net.minestom.server.registry.*;
@@ -14,10 +14,9 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.HashSet;
-import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
-import java.util.function.Function;
/**
* Represents a group of items, blocks, fluids, entity types or function.
@@ -92,32 +91,35 @@ public final class Tag implements ProtocolObject, Keyed {
public enum BasicType {
BLOCKS("minecraft:block", Registry.Resource.BLOCK_TAGS,
- (name, registries) -> Objects.requireNonNull(Block.fromNamespaceId(name)).id()),
+ (blockName, registries) -> Optional.ofNullable(Block.fromNamespaceId(blockName)).map(Block::id)),
ITEMS("minecraft:item", Registry.Resource.ITEM_TAGS,
- (name, registries) -> Objects.requireNonNull(Material.fromNamespaceId(name)).id()),
+ (itemName, registries) -> Optional.ofNullable(Material.fromNamespaceId(itemName)).map(Material::id)),
FLUIDS("minecraft:fluid", Registry.Resource.FLUID_TAGS,
- (name, registries) -> FluidRegistries.getFluid(name).ordinal()),
+ (name, registries) -> Optional.of(name).map(FluidRegistries::getFluid).map(Enum::ordinal)),
ENTITY_TYPES("minecraft:entity_type", Registry.Resource.ENTITY_TYPE_TAGS,
- (name, registries) -> Objects.requireNonNull(EntityType.fromNamespaceId(name)).id()),
+ (entityName, registries) -> Optional.ofNullable(EntityType.fromNamespaceId(entityName)).map(EntityType::id)),
GAME_EVENTS("minecraft:game_event", Registry.Resource.GAMEPLAY_TAGS,
- (name, registries) -> FluidRegistries.getFluid(name).ordinal()),
+ (eventName, registries) -> Optional.ofNullable(GameEvent.fromNamespaceId(eventName)).map(GameEvent::id)),
SOUND_EVENTS("minecraft:sound_event", null, null), // Seems not to be included in server data
POTION_EFFECTS("minecraft:potion_effect", null, null), // Seems not to be included in server data
- //todo this is cursed. it does not update as the registry changes. Fix later.
ENCHANTMENTS("minecraft:enchantment", Registry.Resource.ENCHANTMENT_TAGS,
- (name, registries) -> registries.enchantment().getId(DynamicRegistry.Key.of(name))),
+ (name, registries) -> Optional.of(DynamicRegistry.Key.of(name))
+ .map(DynamicRegistry.Key::namespace)
+ .map(registries.enchantment()::getId)),
BIOMES("minecraft:worldgen/biome", Registry.Resource.BIOME_TAGS,
- (name, registries) -> registries.biome().getId(DynamicRegistry.Key.of(name)));
+ (name, registries) -> Optional.of(DynamicRegistry.Key.of(name))
+ .map(DynamicRegistry.Key::namespace)
+ .map(registries.biome()::getId));
- private final static BasicType[] VALUES = values();
+ private static final BasicType[] VALUES = values();
private final String identifier;
private final Registry.Resource resource;
- private final BiFunction function;
+ private final BiFunction> function;
BasicType(@NotNull String identifier,
@Nullable Registry.Resource resource,
- @Nullable BiFunction function) {
+ @Nullable BiFunction> function) {
this.identifier = identifier;
this.resource = resource;
this.function = function;
@@ -131,7 +133,7 @@ public final class Tag implements ProtocolObject, Keyed {
return resource;
}
- public BiFunction getFunction() {
+ public BiFunction> getFunction() {
return function;
}
diff --git a/src/main/java/net/minestom/server/gamedata/tags/TagManager.java b/src/main/java/net/minestom/server/gamedata/tags/TagManager.java
index 81b0ba01c..f5f9025db 100644
--- a/src/main/java/net/minestom/server/gamedata/tags/TagManager.java
+++ b/src/main/java/net/minestom/server/gamedata/tags/TagManager.java
@@ -30,8 +30,8 @@ public final class TagManager {
public @Nullable Tag getTag(Tag.BasicType type, String namespace) {
final var tags = tagMap.get(type);
- for (var tag : tags) {
- if (tag.getName().asString().equals(namespace))
+ for (final var tag : tags) {
+ if (tag.name().equals(namespace))
return tag;
}
return null;
@@ -46,10 +46,10 @@ public final class TagManager {
for (Map.Entry> entry : tagMap.entrySet()) {
final Tag.BasicType type = entry.getKey();
final String registry = type.getIdentifier();
- List tags = new ArrayList<>();
- for (Tag tag : entry.getValue()) {
- final String identifier = tag.getName().asString();
- final int[] values = tag.getValues().stream().mapToInt(value -> type.getFunction().apply(value.asString(), registries)).toArray();
+ final List tags = new ArrayList<>();
+ for (final Tag tag : entry.getValue()) {
+ final String identifier = tag.name();
+ final int[] values = tag.getValues().stream().mapToInt(value -> type.getFunction().apply(value.asString(), registries).orElse(null)).filter(Objects::nonNull).toArray();
tags.add(new TagsPacket.Tag(identifier, values));
}
registryList.add(new TagsPacket.Registry(registry, tags));
diff --git a/src/main/java/net/minestom/server/instance/DynamicChunk.java b/src/main/java/net/minestom/server/instance/DynamicChunk.java
index dc89d31e3..75eb2db8a 100644
--- a/src/main/java/net/minestom/server/instance/DynamicChunk.java
+++ b/src/main/java/net/minestom/server/instance/DynamicChunk.java
@@ -211,7 +211,7 @@ public class DynamicChunk extends Chunk {
final Section section = getSectionAt(y);
final int blockStateId = section.blockPalette()
.get(globalToSectionRelative(x), globalToSectionRelative(y), globalToSectionRelative(z));
- return Objects.requireNonNullElse(Block.fromStateId((short) blockStateId), Block.AIR);
+ return Objects.requireNonNullElse(Block.fromStateId(blockStateId), Block.AIR);
}
@Override
diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java
index df2ae7b68..0358848fe 100644
--- a/src/main/java/net/minestom/server/instance/Instance.java
+++ b/src/main/java/net/minestom/server/instance/Instance.java
@@ -103,7 +103,7 @@ public abstract class Instance implements Block.Getter, Block.Setter,
private final ChunkCache blockRetriever = new ChunkCache(this, null, null);
// the uuid of this instance
- protected UUID uniqueId;
+ protected UUID uuid;
// instance custom data
protected TagHandler tagHandler = TagHandler.newHandler();
@@ -119,31 +119,31 @@ public abstract class Instance implements Block.Getter, Block.Setter,
/**
* Creates a new instance.
*
- * @param uniqueId the {@link UUID} of the instance
+ * @param uuid the {@link UUID} of the instance
* @param dimensionType the {@link DimensionType} of the instance
*/
- public Instance(@NotNull UUID uniqueId, @NotNull DynamicRegistry.Key dimensionType) {
- this(uniqueId, dimensionType, dimensionType.namespace());
+ public Instance(@NotNull UUID uuid, @NotNull DynamicRegistry.Key dimensionType) {
+ this(uuid, dimensionType, dimensionType.namespace());
}
/**
* Creates a new instance.
*
- * @param uniqueId the {@link UUID} of the instance
+ * @param uuid the {@link UUID} of the instance
* @param dimensionType the {@link DimensionType} of the instance
*/
- public Instance(@NotNull UUID uniqueId, @NotNull DynamicRegistry.Key dimensionType, @NotNull NamespaceID dimensionName) {
- this(MinecraftServer.getDimensionTypeRegistry(), uniqueId, dimensionType, dimensionName);
+ public Instance(@NotNull UUID uuid, @NotNull DynamicRegistry.Key dimensionType, @NotNull NamespaceID dimensionName) {
+ this(MinecraftServer.getDimensionTypeRegistry(), uuid, dimensionType, dimensionName);
}
/**
* Creates a new instance.
*
- * @param uniqueId the {@link UUID} of the instance
+ * @param uuid the {@link UUID} of the instance
* @param dimensionType the {@link DimensionType} of the instance
*/
- public Instance(@NotNull DynamicRegistry dimensionTypeRegistry, @NotNull UUID uniqueId, @NotNull DynamicRegistry.Key dimensionType, @NotNull NamespaceID dimensionName) {
- this.uniqueId = uniqueId;
+ public Instance(@NotNull DynamicRegistry dimensionTypeRegistry, @NotNull UUID uuid, @NotNull DynamicRegistry.Key dimensionType, @NotNull NamespaceID dimensionName) {
+ this.uuid = uuid;
this.dimensionType = dimensionType;
this.cachedDimensionType = dimensionTypeRegistry.get(dimensionType);
Check.argCondition(cachedDimensionType == null, "The dimension " + dimensionType + " is not registered! Please add it to the registry (`MinecraftServer.getDimensionTypeRegistry().registry(dimensionType)`).");
@@ -153,7 +153,7 @@ public abstract class Instance implements Block.Getter, Block.Setter,
targetBorderDiameter = this.worldBorder.diameter();
this.pointers = Pointers.builder()
- .withDynamic(Identity.UUID, this::getUniqueId)
+ .withDynamic(Identity.UUID, this::getUuid)
.build();
final ServerProcess process = MinecraftServer.process();
@@ -750,8 +750,19 @@ public abstract class Instance implements Block.Getter, Block.Setter,
*
* @return the instance unique id
*/
+ public @NotNull UUID getUuid() {
+ return uuid;
+ }
+
+ /**
+ * Gets the instance unique id.
+ *
+ * @return the instance unique id
+ * @deprecated Replace with {@link Instance#getUuid()}
+ */
+ @Deprecated(forRemoval = true)
public @NotNull UUID getUniqueId() {
- return uniqueId;
+ return uuid;
}
/**
diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java
index a1ebbf950..84b2a6af8 100644
--- a/src/main/java/net/minestom/server/instance/InstanceContainer.java
+++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java
@@ -89,30 +89,30 @@ public class InstanceContainer extends Instance {
protected InstanceContainer srcInstance; // only present if this instance has been created using a copy
private long lastBlockChangeTime; // Time at which the last block change happened (#setBlock)
- public InstanceContainer(@NotNull UUID uniqueId, @NotNull DynamicRegistry.Key dimensionType) {
- this(uniqueId, dimensionType, null, dimensionType.namespace());
+ public InstanceContainer(@NotNull UUID uuid, @NotNull DynamicRegistry.Key dimensionType) {
+ this(uuid, dimensionType, null, dimensionType.namespace());
}
- public InstanceContainer(@NotNull UUID uniqueId, @NotNull DynamicRegistry.Key dimensionType, @NotNull NamespaceID dimensionName) {
- this(uniqueId, dimensionType, null, dimensionName);
+ public InstanceContainer(@NotNull UUID uuid, @NotNull DynamicRegistry.Key dimensionType, @NotNull NamespaceID dimensionName) {
+ this(uuid, dimensionType, null, dimensionName);
}
- public InstanceContainer(@NotNull UUID uniqueId, @NotNull DynamicRegistry.Key dimensionType, @Nullable IChunkLoader loader) {
- this(uniqueId, dimensionType, loader, dimensionType.namespace());
+ public InstanceContainer(@NotNull UUID uuid, @NotNull DynamicRegistry.Key dimensionType, @Nullable IChunkLoader loader) {
+ this(uuid, dimensionType, loader, dimensionType.namespace());
}
- public InstanceContainer(@NotNull UUID uniqueId, @NotNull DynamicRegistry.Key dimensionType, @Nullable IChunkLoader loader, @NotNull NamespaceID dimensionName) {
- this(MinecraftServer.getDimensionTypeRegistry(), uniqueId, dimensionType, loader, dimensionName);
+ public InstanceContainer(@NotNull UUID uuid, @NotNull DynamicRegistry.Key dimensionType, @Nullable IChunkLoader loader, @NotNull NamespaceID dimensionName) {
+ this(MinecraftServer.getDimensionTypeRegistry(), uuid, dimensionType, loader, dimensionName);
}
public InstanceContainer(
@NotNull DynamicRegistry dimensionTypeRegistry,
- @NotNull UUID uniqueId,
+ @NotNull UUID uuid,
@NotNull DynamicRegistry.Key dimensionType,
@Nullable IChunkLoader loader,
@NotNull NamespaceID dimensionName
) {
- super(dimensionTypeRegistry, uniqueId, dimensionType, dimensionName);
+ super(dimensionTypeRegistry, uuid, dimensionType, dimensionName);
setChunkSupplier(DynamicChunk::new);
setChunkLoader(Objects.requireNonNullElse(loader, DEFAULT_LOADER));
this.chunkLoader.loadInstance(this);
diff --git a/src/main/java/net/minestom/server/instance/InstanceManager.java b/src/main/java/net/minestom/server/instance/InstanceManager.java
index 6b4bc2d12..8fed6b6d9 100644
--- a/src/main/java/net/minestom/server/instance/InstanceManager.java
+++ b/src/main/java/net/minestom/server/instance/InstanceManager.java
@@ -151,7 +151,7 @@ public final class InstanceManager {
public @Nullable Instance getInstance(@NotNull UUID uuid) {
Optional instance = getInstances()
.stream()
- .filter(someInstance -> someInstance.getUniqueId().equals(uuid))
+ .filter(someInstance -> someInstance.getUuid().equals(uuid))
.findFirst();
return instance.orElse(null);
}
diff --git a/src/main/java/net/minestom/server/instance/LightingChunk.java b/src/main/java/net/minestom/server/instance/LightingChunk.java
index c8872e363..99126db98 100644
--- a/src/main/java/net/minestom/server/instance/LightingChunk.java
+++ b/src/main/java/net/minestom/server/instance/LightingChunk.java
@@ -241,97 +241,98 @@ public class LightingChunk extends DynamicChunk {
@Override
protected LightData createLightData(boolean requiredFullChunk) {
packetGenerationLock.lock();
- if (requiredFullChunk) {
- if (fullLightData != null) {
- packetGenerationLock.unlock();
- return fullLightData;
- }
- } else {
- if (partialLightData != null) {
- packetGenerationLock.unlock();
- return partialLightData;
- }
- }
-
- BitSet skyMask = new BitSet();
- BitSet blockMask = new BitSet();
- BitSet emptySkyMask = new BitSet();
- BitSet emptyBlockMask = new BitSet();
- List skyLights = new ArrayList<>();
- List blockLights = new ArrayList<>();
-
- int chunkMin = instance.getCachedDimensionType().minY();
- int highestNeighborBlock = instance.getCachedDimensionType().minY();
- for (int i = -1; i <= 1; i++) {
- for (int j = -1; j <= 1; j++) {
- Chunk neighborChunk = instance.getChunk(chunkX + i, chunkZ + j);
- if (neighborChunk == null) continue;
-
- if (neighborChunk instanceof LightingChunk light) {
- light.getOcclusionMap();
- highestNeighborBlock = Math.max(highestNeighborBlock, light.highestBlock);
+ try {
+ if (requiredFullChunk) {
+ if (fullLightData != null) {
+ return fullLightData;
}
- }
- }
-
- int index = 0;
- for (Section section : sections) {
- boolean wasUpdatedBlock = false;
- boolean wasUpdatedSky = false;
-
- if (section.blockLight().requiresUpdate()) {
- relightSection(instance, this.chunkX, index + minSection, chunkZ, LightType.BLOCK);
- wasUpdatedBlock = true;
- } else if (requiredFullChunk || section.blockLight().requiresSend()) {
- wasUpdatedBlock = true;
- }
-
- if (section.skyLight().requiresUpdate()) {
- relightSection(instance, this.chunkX, index + minSection, chunkZ, LightType.SKY);
- wasUpdatedSky = true;
- } else if (requiredFullChunk || section.skyLight().requiresSend()) {
- wasUpdatedSky = true;
- }
-
- final int sectionMinY = index * 16 + chunkMin;
- index++;
-
- if ((wasUpdatedSky) && this.instance.getCachedDimensionType().hasSkylight() && sectionMinY <= (highestNeighborBlock + 16)) {
- final byte[] skyLight = section.skyLight().array();
-
- if (skyLight.length != 0 && skyLight != EMPTY_CONTENT) {
- skyLights.add(skyLight);
- skyMask.set(index);
- } else {
- emptySkyMask.set(index);
+ } else {
+ if (partialLightData != null) {
+ return partialLightData;
}
}
- if (wasUpdatedBlock) {
- final byte[] blockLight = section.blockLight().array();
+ BitSet skyMask = new BitSet();
+ BitSet blockMask = new BitSet();
+ BitSet emptySkyMask = new BitSet();
+ BitSet emptyBlockMask = new BitSet();
+ List skyLights = new ArrayList<>();
+ List blockLights = new ArrayList<>();
- if (blockLight.length != 0 && blockLight != EMPTY_CONTENT) {
- blockLights.add(blockLight);
- blockMask.set(index);
- } else {
- emptyBlockMask.set(index);
+ int chunkMin = instance.getCachedDimensionType().minY();
+ int highestNeighborBlock = instance.getCachedDimensionType().minY();
+ for (int i = -1; i <= 1; i++) {
+ for (int j = -1; j <= 1; j++) {
+ Chunk neighborChunk = instance.getChunk(chunkX + i, chunkZ + j);
+ if (neighborChunk == null) continue;
+
+ if (neighborChunk instanceof LightingChunk light) {
+ light.getOcclusionMap();
+ highestNeighborBlock = Math.max(highestNeighborBlock, light.highestBlock);
+ }
}
}
+
+ int index = 0;
+ for (Section section : sections) {
+ boolean wasUpdatedBlock = false;
+ boolean wasUpdatedSky = false;
+
+ if (section.blockLight().requiresUpdate()) {
+ relightSection(instance, this.chunkX, index + minSection, chunkZ, LightType.BLOCK);
+ wasUpdatedBlock = true;
+ } else if (requiredFullChunk || section.blockLight().requiresSend()) {
+ wasUpdatedBlock = true;
+ }
+
+ if (section.skyLight().requiresUpdate()) {
+ relightSection(instance, this.chunkX, index + minSection, chunkZ, LightType.SKY);
+ wasUpdatedSky = true;
+ } else if (requiredFullChunk || section.skyLight().requiresSend()) {
+ wasUpdatedSky = true;
+ }
+
+ final int sectionMinY = index * 16 + chunkMin;
+ index++;
+
+ if ((wasUpdatedSky) && this.instance.getCachedDimensionType().hasSkylight() && sectionMinY <= (highestNeighborBlock + 16)) {
+ final byte[] skyLight = section.skyLight().array();
+
+ if (skyLight.length != 0 && skyLight != EMPTY_CONTENT) {
+ skyLights.add(skyLight);
+ skyMask.set(index);
+ } else {
+ emptySkyMask.set(index);
+ }
+ }
+
+ if (wasUpdatedBlock) {
+ final byte[] blockLight = section.blockLight().array();
+
+ if (blockLight.length != 0 && blockLight != EMPTY_CONTENT) {
+ blockLights.add(blockLight);
+ blockMask.set(index);
+ } else {
+ emptyBlockMask.set(index);
+ }
+ }
+ }
+
+ LightData lightData = new LightData(skyMask, blockMask,
+ emptySkyMask, emptyBlockMask,
+ skyLights, blockLights);
+
+ if (requiredFullChunk) {
+ this.fullLightData = lightData;
+ } else {
+ this.partialLightData = lightData;
+ }
+
+
+ return lightData;
+ } finally {
+ packetGenerationLock.unlock();
}
-
- LightData lightData = new LightData(skyMask, blockMask,
- emptySkyMask, emptyBlockMask,
- skyLights, blockLights);
-
- if (requiredFullChunk) {
- this.fullLightData = lightData;
- } else {
- this.partialLightData = lightData;
- }
-
- packetGenerationLock.unlock();
-
- return lightData;
}
@Override
diff --git a/src/main/java/net/minestom/server/instance/SharedInstance.java b/src/main/java/net/minestom/server/instance/SharedInstance.java
index c01540fa8..10a1cd217 100644
--- a/src/main/java/net/minestom/server/instance/SharedInstance.java
+++ b/src/main/java/net/minestom/server/instance/SharedInstance.java
@@ -21,8 +21,8 @@ import java.util.concurrent.CompletableFuture;
public class SharedInstance extends Instance {
private final InstanceContainer instanceContainer;
- public SharedInstance(@NotNull UUID uniqueId, @NotNull InstanceContainer instanceContainer) {
- super(uniqueId, instanceContainer.getDimensionType());
+ public SharedInstance(@NotNull UUID uuid, @NotNull InstanceContainer instanceContainer) {
+ super(uuid, instanceContainer.getDimensionType());
this.instanceContainer = instanceContainer;
}
diff --git a/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java b/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java
index f6ac7795d..81dda9ecc 100644
--- a/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java
+++ b/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java
@@ -106,7 +106,7 @@ public class ChunkBatch implements Batch {
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
if (chunk == null) {
LOGGER.warn("Unable to apply ChunkBatch to unloaded chunk ({}, {}) in {}.",
- chunkX, chunkZ, instance.getUniqueId());
+ chunkX, chunkZ, instance.getUuid());
return null;
}
return apply(instance, chunk, callback);
@@ -165,7 +165,7 @@ public class ChunkBatch implements Batch {
try {
if (!chunk.isLoaded()) {
LOGGER.warn("Unable to apply ChunkBatch to unloaded chunk ({}, {}) in {}.",
- chunk.getChunkX(), chunk.getChunkZ(), instance.getUniqueId());
+ chunk.getChunkX(), chunk.getChunkZ(), instance.getUuid());
return;
}
diff --git a/src/main/java/net/minestom/server/instance/block/banner/BannerPattern.java b/src/main/java/net/minestom/server/instance/block/banner/BannerPattern.java
index 5b7774ba1..7fbf7307a 100644
--- a/src/main/java/net/minestom/server/instance/block/banner/BannerPattern.java
+++ b/src/main/java/net/minestom/server/instance/block/banner/BannerPattern.java
@@ -13,7 +13,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public sealed interface BannerPattern extends ProtocolObject, BannerPatterns permits BannerPatternImpl {
- @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::bannerPattern);
+ @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::bannerPattern, true);
@NotNull BinaryTagSerializer> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::bannerPattern);
static @NotNull BannerPattern create(
diff --git a/src/main/java/net/minestom/server/instance/block/jukebox/JukeboxSong.java b/src/main/java/net/minestom/server/instance/block/jukebox/JukeboxSong.java
index 51dce5e39..f82e14fc5 100644
--- a/src/main/java/net/minestom/server/instance/block/jukebox/JukeboxSong.java
+++ b/src/main/java/net/minestom/server/instance/block/jukebox/JukeboxSong.java
@@ -14,7 +14,7 @@ import org.jetbrains.annotations.Nullable;
public sealed interface JukeboxSong extends ProtocolObject, JukeboxSongs permits JukeboxSongImpl {
- @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::jukeboxSong);
+ @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::jukeboxSong, false);
@NotNull BinaryTagSerializer> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::jukeboxSong);
static @NotNull JukeboxSong create(
diff --git a/src/main/java/net/minestom/server/instance/generator/UnitModifier.java b/src/main/java/net/minestom/server/instance/generator/UnitModifier.java
index dfd297232..406a59bdb 100644
--- a/src/main/java/net/minestom/server/instance/generator/UnitModifier.java
+++ b/src/main/java/net/minestom/server/instance/generator/UnitModifier.java
@@ -48,7 +48,7 @@ public interface UnitModifier extends Block.Setter, Biome.Setter {
void fill(@NotNull Point start, @NotNull Point end, @NotNull Block block);
/**
- * Fills the 3d rectangular area with the given biome.
+ * Fills the 3d rectangular area with the given block.
*
* @param minHeight the minimum height of the area
* @param maxHeight the maximum height of the area
diff --git a/src/main/java/net/minestom/server/instance/light/BlockLight.java b/src/main/java/net/minestom/server/instance/light/BlockLight.java
index 244940fc4..751c461c1 100644
--- a/src/main/java/net/minestom/server/instance/light/BlockLight.java
+++ b/src/main/java/net/minestom/server/instance/light/BlockLight.java
@@ -33,7 +33,7 @@ final class BlockLight implements Light {
ShortArrayFIFOQueue lightSources = new ShortArrayFIFOQueue();
// Apply section light
blockPalette.getAllPresent((x, y, z, stateId) -> {
- final Block block = Block.fromStateId((short) stateId);
+ final Block block = Block.fromStateId(stateId);
assert block != null;
final byte lightEmission = (byte) block.registry().lightEmission();
diff --git a/src/main/java/net/minestom/server/instance/light/LightCompute.java b/src/main/java/net/minestom/server/instance/light/LightCompute.java
index 591e5d21c..43d92abd7 100644
--- a/src/main/java/net/minestom/server/instance/light/LightCompute.java
+++ b/src/main/java/net/minestom/server/instance/light/LightCompute.java
@@ -112,7 +112,7 @@ public final class LightCompute {
}
public static Block getBlock(Palette palette, int x, int y, int z) {
- return Block.fromStateId((short) palette.get(x, y, z));
+ return Block.fromStateId(palette.get(x, y, z));
}
public static byte[] bake(byte[] content1, byte[] content2) {
diff --git a/src/main/java/net/minestom/server/instance/palette/Palette.java b/src/main/java/net/minestom/server/instance/palette/Palette.java
index 755d8c309..9acd0b24f 100644
--- a/src/main/java/net/minestom/server/instance/palette/Palette.java
+++ b/src/main/java/net/minestom/server/instance/palette/Palette.java
@@ -115,6 +115,7 @@ public interface Palette {
if (bitsPerEntry == 0) {
// Single valued 0-0
final int value = buffer.read(VAR_INT);
+ buffer.read(VAR_INT); // Skip size
return new PaletteSingle((byte) dimension, value);
} else if (bitsPerEntry >= minIndirect && bitsPerEntry <= maxIndirect) {
// Indirect palette
diff --git a/src/main/java/net/minestom/server/instance/palette/PaletteIndirect.java b/src/main/java/net/minestom/server/instance/palette/PaletteIndirect.java
index 3cd3aa2e2..eff381467 100644
--- a/src/main/java/net/minestom/server/instance/palette/PaletteIndirect.java
+++ b/src/main/java/net/minestom/server/instance/palette/PaletteIndirect.java
@@ -49,8 +49,6 @@ final class PaletteIndirect implements SpecializedPalette, Cloneable {
this.paletteToValueList.add(palette[i]);
this.valueToPaletteMap.put(palette[i], i);
}
-
- this.values = new long[arrayLength(dimension(), bitsPerEntry)];
}
PaletteIndirect(int dimension, int maxBitsPerEntry, byte bitsPerEntry) {
diff --git a/src/main/java/net/minestom/server/item/ItemComponent.java b/src/main/java/net/minestom/server/item/ItemComponent.java
index 849c8a0cc..8de53eff8 100644
--- a/src/main/java/net/minestom/server/item/ItemComponent.java
+++ b/src/main/java/net/minestom/server/item/ItemComponent.java
@@ -77,7 +77,7 @@ public final class ItemComponent {
public static final DataComponent WRITABLE_BOOK_CONTENT = register("writable_book_content", WritableBookContent.NETWORK_TYPE, WritableBookContent.NBT_TYPE);
public static final DataComponent WRITTEN_BOOK_CONTENT = register("written_book_content", WrittenBookContent.NETWORK_TYPE, WrittenBookContent.NBT_TYPE);
public static final DataComponent TRIM = register("trim", ArmorTrim.NETWORK_TYPE, ArmorTrim.NBT_TYPE);
- public static final DataComponent DEBUG_STICK_STATE = register("debug_stick_state", null, DebugStickState.NBT_TYPE);
+ public static final DataComponent DEBUG_STICK_STATE = register("debug_stick_state", DebugStickState.NETWORK_TYPE, DebugStickState.NBT_TYPE);
public static final DataComponent ENTITY_DATA = register("entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE);
public static final DataComponent BUCKET_ENTITY_DATA = register("bucket_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE);
public static final DataComponent BLOCK_ENTITY_DATA = register("block_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE);
diff --git a/src/main/java/net/minestom/server/item/armor/TrimMaterial.java b/src/main/java/net/minestom/server/item/armor/TrimMaterial.java
index 610b2e59c..e215f1174 100644
--- a/src/main/java/net/minestom/server/item/armor/TrimMaterial.java
+++ b/src/main/java/net/minestom/server/item/armor/TrimMaterial.java
@@ -17,7 +17,7 @@ import java.util.HashMap;
import java.util.Map;
public sealed interface TrimMaterial extends ProtocolObject permits TrimMaterialImpl {
- @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::trimMaterial);
+ @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::trimMaterial, true);
@NotNull BinaryTagSerializer> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::trimMaterial);
static @NotNull TrimMaterial create(
diff --git a/src/main/java/net/minestom/server/item/armor/TrimPattern.java b/src/main/java/net/minestom/server/item/armor/TrimPattern.java
index bfe2b76a1..9c6988fda 100644
--- a/src/main/java/net/minestom/server/item/armor/TrimPattern.java
+++ b/src/main/java/net/minestom/server/item/armor/TrimPattern.java
@@ -15,7 +15,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public sealed interface TrimPattern extends ProtocolObject permits TrimPatternImpl {
- @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::trimPattern);
+ @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::trimPattern, true);
@NotNull BinaryTagSerializer> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::trimPattern);
static @NotNull TrimPattern create(
diff --git a/src/main/java/net/minestom/server/item/component/DebugStickState.java b/src/main/java/net/minestom/server/item/component/DebugStickState.java
index 8c2116955..1af3d4229 100644
--- a/src/main/java/net/minestom/server/item/component/DebugStickState.java
+++ b/src/main/java/net/minestom/server/item/component/DebugStickState.java
@@ -3,6 +3,7 @@ package net.minestom.server.item.component;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.StringBinaryTag;
+import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
@@ -30,6 +31,7 @@ public record DebugStickState(@NotNull Map state) {
return builder.build();
}
);
+ public static final NetworkBuffer.Type NETWORK_TYPE = NetworkBuffer.TypedNBT(NBT_TYPE);
public DebugStickState {
state = Map.copyOf(state);
diff --git a/src/main/java/net/minestom/server/item/enchant/Enchantment.java b/src/main/java/net/minestom/server/item/enchant/Enchantment.java
index 91b345b06..020010d13 100644
--- a/src/main/java/net/minestom/server/item/enchant/Enchantment.java
+++ b/src/main/java/net/minestom/server/item/enchant/Enchantment.java
@@ -16,7 +16,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
public sealed interface Enchantment extends ProtocolObject, Enchantments permits EnchantmentImpl {
- @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::enchantment);
+ @NotNull NetworkBuffer.Type> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::enchantment, false);
@NotNull BinaryTagSerializer> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::enchantment);
static @NotNull Builder builder() {
diff --git a/src/main/java/net/minestom/server/listener/SpectateListener.java b/src/main/java/net/minestom/server/listener/SpectateListener.java
index f528fd26e..7c4c044ef 100644
--- a/src/main/java/net/minestom/server/listener/SpectateListener.java
+++ b/src/main/java/net/minestom/server/listener/SpectateListener.java
@@ -35,7 +35,7 @@ public class SpectateListener {
// Ignore if they're not in the same instance. Vanilla actually allows for
// cross-instance spectating, but it's not really a good idea for Minestom.
- if (targetInstance.getUniqueId() != playerInstance.getUniqueId()) {
+ if (targetInstance.getUuid() != playerInstance.getUuid()) {
return;
}
diff --git a/src/main/java/net/minestom/server/listener/UseEntityListener.java b/src/main/java/net/minestom/server/listener/UseEntityListener.java
index 246844963..819c32ca2 100644
--- a/src/main/java/net/minestom/server/listener/UseEntityListener.java
+++ b/src/main/java/net/minestom/server/listener/UseEntityListener.java
@@ -1,7 +1,9 @@
package net.minestom.server.listener;
import net.minestom.server.ServerFlag;
+import net.minestom.server.collision.BoundingBox;
import net.minestom.server.coordinate.Point;
+import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.LivingEntity;
@@ -20,8 +22,11 @@ public class UseEntityListener {
return;
if (ServerFlag.ENFORCE_INTERACTION_LIMIT) {
- double range = Math.pow(player.getAttributeValue(Attribute.ENTITY_INTERACTION_RANGE) + 1, 2); // Add 1 additional block for people with less than stellar ping
- if (player.getDistanceSquared(entity) > range) {
+ final double maxDistanceSquared = Math.pow(player.getAttributeValue(Attribute.ENTITY_INTERACTION_RANGE) + 1, 2);
+
+ final double distSquared = getDistSquared(player, entity);
+
+ if (distSquared > maxDistanceSquared) {
return;
}
}
@@ -36,4 +41,33 @@ public class UseEntityListener {
EventDispatcher.call(new PlayerEntityInteractEvent(player, entity, interactAt.hand(), interactPosition));
}
}
-}
+
+ private static double getDistSquared(Player player, Entity entity) {
+ final Pos playerPos = player.getPosition();
+ final double eyeHeight = player.getEyeHeight();
+ final double px = playerPos.x();
+ final double py = playerPos.y() + eyeHeight;
+ final double pz = playerPos.z();
+
+ final BoundingBox box = entity.getBoundingBox();
+ final double halfWidth = box.width() / 2;
+ final double height = box.height();
+ final Pos entityPos = entity.getPosition();
+
+ final double minX = entityPos.x() - halfWidth;
+ final double maxX = entityPos.x() + halfWidth;
+ final double minY = entityPos.y();
+ final double maxY = entityPos.y() + height;
+ final double minZ = entityPos.z() - halfWidth;
+ final double maxZ = entityPos.z() + halfWidth;
+
+ final double clampX = Math.max(minX, Math.min(px, maxX));
+ final double clampY = Math.max(minY, Math.min(py, maxY));
+ final double clampZ = Math.max(minZ, Math.min(pz, maxZ));
+
+ final double dx = px - clampX;
+ final double dy = py - clampY;
+ final double dz = pz - clampZ;
+ return dx * dx + dy * dy + dz * dz;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/minestom/server/network/ComponentNetworkBufferTypeImpl.java b/src/main/java/net/minestom/server/network/ComponentNetworkBufferTypeImpl.java
index 3fcacd3fb..9b2ec3ff6 100644
--- a/src/main/java/net/minestom/server/network/ComponentNetworkBufferTypeImpl.java
+++ b/src/main/java/net/minestom/server/network/ComponentNetworkBufferTypeImpl.java
@@ -45,33 +45,33 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl {
- writeUtf(buffer, "text");
+ buffer.write(STRING_IO_UTF8, "text");
buffer.write(BYTE, TAG_STRING); // Start "text" tag
- writeUtf(buffer, "text");
- writeUtf(buffer, text.content());
+ buffer.write(STRING_IO_UTF8, "text");
+ buffer.write(STRING_IO_UTF8, text.content());
}
case TranslatableComponent translatable -> {
- writeUtf(buffer, "translatable");
+ buffer.write(STRING_IO_UTF8, "translatable");
buffer.write(BYTE, TAG_STRING); // Start "translate" tag
- writeUtf(buffer, "translate");
- writeUtf(buffer, translatable.key());
+ buffer.write(STRING_IO_UTF8, "translate");
+ buffer.write(STRING_IO_UTF8, translatable.key());
final String fallback = translatable.fallback();
if (fallback != null) {
buffer.write(BYTE, TAG_STRING);
- writeUtf(buffer, "fallback");
- writeUtf(buffer, fallback);
+ buffer.write(STRING_IO_UTF8, "fallback");
+ buffer.write(STRING_IO_UTF8, fallback);
}
final List args = translatable.arguments();
if (!args.isEmpty()) {
buffer.write(BYTE, TAG_LIST);
- writeUtf(buffer, "with");
+ buffer.write(STRING_IO_UTF8, "with");
buffer.write(BYTE, TAG_COMPOUND); // List type
buffer.write(INT, args.size());
for (final TranslationArgument arg : args)
@@ -79,42 +79,42 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl {
- writeUtf(buffer, "score");
+ buffer.write(STRING_IO_UTF8, "score");
buffer.write(BYTE, TAG_COMPOUND); // Start "score" tag
- writeUtf(buffer, "score");
+ buffer.write(STRING_IO_UTF8, "score");
{
buffer.write(BYTE, TAG_STRING);
- writeUtf(buffer, "name");
- writeUtf(buffer, score.name());
+ buffer.write(STRING_IO_UTF8, "name");
+ buffer.write(STRING_IO_UTF8, score.name());
buffer.write(BYTE, TAG_STRING);
- writeUtf(buffer, "objective");
- writeUtf(buffer, score.objective());
+ buffer.write(STRING_IO_UTF8, "objective");
+ buffer.write(STRING_IO_UTF8, score.objective());
}
buffer.write(BYTE, TAG_END); // End "score" tag
}
case SelectorComponent selector -> {
- writeUtf(buffer, "selector");
+ buffer.write(STRING_IO_UTF8, "selector");
buffer.write(BYTE, TAG_STRING);
- writeUtf(buffer, "selector");
- writeUtf(buffer, selector.pattern());
+ buffer.write(STRING_IO_UTF8, "selector");
+ buffer.write(STRING_IO_UTF8, selector.pattern());
final Component separator = selector.separator();
if (separator != null) {
buffer.write(BYTE, TAG_COMPOUND);
- writeUtf(buffer, "separator");
+ buffer.write(STRING_IO_UTF8, "separator");
writeInnerComponent(buffer, separator);
}
}
case KeybindComponent keybind -> {
- writeUtf(buffer, "keybind");
+ buffer.write(STRING_IO_UTF8, "keybind");
buffer.write(BYTE, TAG_STRING);
- writeUtf(buffer, "keybind");
- writeUtf(buffer, keybind.keybind());
+ buffer.write(STRING_IO_UTF8, "keybind");
+ buffer.write(STRING_IO_UTF8, keybind.keybind());
}
case NBTComponent, ?> nbt -> {
//todo
@@ -126,7 +126,7 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl hoverEvent) {
buffer.write(BYTE, TAG_COMPOUND);
- writeUtf(buffer, "hoverEvent");
+ buffer.write(STRING_IO_UTF8, "hoverEvent");
buffer.write(BYTE, TAG_STRING);
- writeUtf(buffer, "action");
- writeUtf(buffer, hoverEvent.action().toString().toLowerCase(Locale.ROOT));
+ buffer.write(STRING_IO_UTF8, "action");
+ buffer.write(STRING_IO_UTF8, hoverEvent.action().toString().toLowerCase(Locale.ROOT));
buffer.write(BYTE, TAG_COMPOUND); // Start contents tag
- writeUtf(buffer, "contents");
+ buffer.write(STRING_IO_UTF8, "contents");
if (hoverEvent.action() == HoverEvent.Action.SHOW_TEXT) {
writeInnerComponent(buffer, (Component) hoverEvent.value());
} else if (hoverEvent.action() == HoverEvent.Action.SHOW_ITEM) {
var value = ((HoverEvent) hoverEvent).value();
buffer.write(BYTE, TAG_STRING);
- writeUtf(buffer, "id");
- writeUtf(buffer, value.item().asString());
+ buffer.write(STRING_IO_UTF8, "id");
+ buffer.write(STRING_IO_UTF8, value.item().asString());
buffer.write(BYTE, TAG_INT);
- writeUtf(buffer, "count");
+ buffer.write(STRING_IO_UTF8, "count");
buffer.write(INT, value.count());
buffer.write(BYTE, TAG_COMPOUND);
- writeUtf(buffer, "components");
+ buffer.write(STRING_IO_UTF8, "components");
//todo item components
buffer.write(BYTE, TAG_END);
@@ -257,17 +257,17 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl= 0x80 || c == 0)
- utflen += (c >= 0x800) ? 2 : 1;
- }
-
- if (utflen > 65535 || /* overflow */ utflen < strlen)
- throw new RuntimeException("UTF-8 string too long");
-
- buffer.write(SHORT, (short) utflen);
- buffer.ensureWritable(utflen);
- var impl = (NetworkBufferImpl) buffer;
- int i;
- for (i = 0; i < strlen; i++) { // optimized for initial run of ASCII
- int c = str.charAt(i);
- if (c >= 0x80 || c == 0) break;
- impl._putByte(buffer.writeIndex(), (byte) c);
- impl.advanceWrite(1);
- }
-
- for (; i < strlen; i++) {
- int c = str.charAt(i);
- if (c < 0x80 && c != 0) {
- impl._putByte(buffer.writeIndex(), (byte) c);
- impl.advanceWrite(1);
- } else if (c >= 0x800) {
- impl._putByte(buffer.writeIndex(), (byte) (0xE0 | ((c >> 12) & 0x0F)));
- impl._putByte(buffer.writeIndex() + 1, (byte) (0x80 | ((c >> 6) & 0x3F)));
- impl._putByte(buffer.writeIndex() + 2, (byte) (0x80 | ((c >> 0) & 0x3F)));
- impl.advanceWrite(3);
- } else {
- impl._putByte(buffer.writeIndex(), (byte) (0xC0 | ((c >> 6) & 0x1F)));
- impl._putByte(buffer.writeIndex() + 1, (byte) (0x80 | ((c >> 0) & 0x3F)));
- impl.advanceWrite(2);
- }
- }
- }
}
diff --git a/src/main/java/net/minestom/server/network/ConnectionManager.java b/src/main/java/net/minestom/server/network/ConnectionManager.java
index ec2cd9cb7..597fc061f 100644
--- a/src/main/java/net/minestom/server/network/ConnectionManager.java
+++ b/src/main/java/net/minestom/server/network/ConnectionManager.java
@@ -333,6 +333,7 @@ public final class ConnectionManager {
public void updateWaitingPlayers() {
this.waitingPlayers.drain(player -> {
if (!player.isOnline()) return; // Player disconnected while in queued to join
+ configurationPlayers.remove(player);
playPlayers.add(player);
keepAlivePlayers.add(player);
diff --git a/src/main/java/net/minestom/server/network/NetworkBuffer.java b/src/main/java/net/minestom/server/network/NetworkBuffer.java
index 068e34b36..bb07e2dc8 100644
--- a/src/main/java/net/minestom/server/network/NetworkBuffer.java
+++ b/src/main/java/net/minestom/server/network/NetworkBuffer.java
@@ -12,6 +12,7 @@ import net.minestom.server.registry.Registries;
import net.minestom.server.utils.Direction;
import net.minestom.server.utils.Unit;
import net.minestom.server.utils.crypto.KeyUtils;
+import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
@@ -45,6 +46,7 @@ public sealed interface NetworkBuffer permits NetworkBufferImpl {
Type RAW_BYTES = new NetworkBufferTypeImpl.RawBytesType(-1);
Type STRING = new NetworkBufferTypeImpl.StringType();
Type STRING_TERMINATED = new NetworkBufferTypeImpl.StringTerminatedType();
+ Type STRING_IO_UTF8 = new NetworkBufferTypeImpl.IOUTF8StringType();
Type NBT = new NetworkBufferTypeImpl.NbtType();
@SuppressWarnings({"unchecked", "rawtypes"})
Type NBT_COMPOUND = (Type) new NetworkBufferTypeImpl.NbtType();
@@ -63,8 +65,8 @@ public sealed interface NetworkBuffer permits NetworkBufferImpl {
Type INSTANT_MS = LONG.transform(Instant::ofEpochMilli, Instant::toEpochMilli);
Type PUBLIC_KEY = BYTE_ARRAY.transform(KeyUtils::publicRSAKeyFrom, PublicKey::getEncoded);
- static @NotNull Type> RegistryKey(@NotNull Function> selector) {
- return new NetworkBufferTypeImpl.RegistryTypeType<>(selector);
+ static @NotNull Type> RegistryKey(@NotNull Function> selector, boolean holder) {
+ return new NetworkBufferTypeImpl.RegistryTypeType<>(selector, holder);
}
// METADATA
@@ -104,6 +106,10 @@ public sealed interface NetworkBuffer permits NetworkBufferImpl {
return new NetworkBufferTypeImpl.LazyType<>(supplier);
}
+ static @NotNull Type TypedNBT(@NotNull BinaryTagSerializer serializer) {
+ return new NetworkBufferTypeImpl.TypedNbtType<>(serializer);
+ }
+
void write(@NotNull Type type, @UnknownNullability T value) throws IndexOutOfBoundsException;
@UnknownNullability T read(@NotNull Type type) throws IndexOutOfBoundsException;
diff --git a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java
index b7c2efd8d..4da1b1c86 100644
--- a/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java
+++ b/src/main/java/net/minestom/server/network/NetworkBufferTypeImpl.java
@@ -13,6 +13,7 @@ import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.Unit;
import net.minestom.server.utils.nbt.BinaryTagReader;
+import net.minestom.server.utils.nbt.BinaryTagSerializer;
import net.minestom.server.utils.nbt.BinaryTagWriter;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
@@ -708,6 +709,23 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type {
}
}
+ record TypedNbtType(@NotNull BinaryTagSerializer nbtType) implements NetworkBufferTypeImpl {
+ @Override
+ public void write(@NotNull NetworkBuffer buffer, T value) {
+ final Registries registries = impl(buffer).registries;
+ Check.stateCondition(registries == null, "Buffer does not have registries");
+ buffer.write(NBT, nbtType.write(new BinaryTagSerializer.ContextWithRegistries(registries), value));
+ }
+
+ @Override
+ public T read(@NotNull NetworkBuffer buffer) {
+ final Registries registries = impl(buffer).registries;
+ Check.stateCondition(registries == null, "Buffer does not have registries");
+ final BinaryTag tag = buffer.read(NBT);
+ return nbtType.read(new BinaryTagSerializer.ContextWithRegistries(registries), tag);
+ }
+ }
+
record TransformType(@NotNull Type parent, @NotNull Function to,
@NotNull Function from) implements NetworkBufferTypeImpl {
@Override
@@ -794,15 +812,18 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type {
}
record RegistryTypeType(
- @NotNull Function> selector) implements NetworkBufferTypeImpl> {
+ @NotNull Function> selector,
+ boolean holder
+ ) implements NetworkBufferTypeImpl> {
@Override
public void write(@NotNull NetworkBuffer buffer, DynamicRegistry.Key value) {
final Registries registries = impl(buffer).registries;
Check.stateCondition(registries == null, "Buffer does not have registries");
final DynamicRegistry registry = selector.apply(registries);
- // Painting variants may be sent in their entirety rather than a registry reference so the ID is offset by 1 to indicate this.
+ // "Holder" references can either be a registry entry or the entire object itself. The id is zero if the
+ // entire object follows, but we only support registry objects currently so always offset by 1.
// FIXME: Support sending the entire registry object instead of an ID reference.
- final int id = registry.id().equals("minecraft:painting_variant") ? registry.getId(value) + 1 : registry.getId(value);
+ final int id = registry.getId(value) + (holder ? 1 : 0);
Check.argCondition(id == -1, "Key is not registered: {0} > {1}", registry, value);
buffer.write(VAR_INT, id);
}
@@ -812,13 +833,131 @@ interface NetworkBufferTypeImpl extends NetworkBuffer.Type {
final Registries registries = impl(buffer).registries;
Check.stateCondition(registries == null, "Buffer does not have registries");
DynamicRegistry registry = selector.apply(registries);
- final int id = buffer.read(VAR_INT);
+ // See note above about holder references.
+ final int id = buffer.read(VAR_INT) + (holder ? -1 : 0);
final DynamicRegistry.Key key = registry.getKey(id);
Check.argCondition(key == null, "No such ID in registry: {0} > {1}", registry, id);
return key;
}
}
+ /**
+ * This is a very gross version of {@link java.io.DataOutputStream#writeUTF(String)} & ${@link DataInputStream#readUTF()}. We need the data in the java
+ * modified utf-8 format for Component, and I couldnt find a method without creating a new buffer for it.
+ */
+ record IOUTF8StringType() implements NetworkBufferTypeImpl {
+ @Override
+ public void write(@NotNull NetworkBuffer buffer, String value) {
+ final int strlen = value.length();
+ int utflen = strlen; // optimized for ASCII
+
+ for (int i = 0; i < strlen; i++) {
+ int c = value.charAt(i);
+ if (c >= 0x80 || c == 0)
+ utflen += (c >= 0x800) ? 2 : 1;
+ }
+
+ if (utflen > 65535 || /* overflow */ utflen < strlen)
+ throw new RuntimeException("UTF-8 string too long");
+
+ buffer.write(SHORT, (short) utflen);
+ buffer.ensureWritable(utflen);
+ var impl = (NetworkBufferImpl) buffer;
+ int i;
+ for (i = 0; i < strlen; i++) { // optimized for initial run of ASCII
+ int c = value.charAt(i);
+ if (c >= 0x80 || c == 0) break;
+ impl._putByte(buffer.writeIndex(), (byte) c);
+ impl.advanceWrite(1);
+ }
+
+ for (; i < strlen; i++) {
+ int c = value.charAt(i);
+ if (c < 0x80 && c != 0) {
+ impl._putByte(buffer.writeIndex(), (byte) c);
+ impl.advanceWrite(1);
+ } else if (c >= 0x800) {
+ impl._putByte(buffer.writeIndex(), (byte) (0xE0 | ((c >> 12) & 0x0F)));
+ impl._putByte(buffer.writeIndex() + 1, (byte) (0x80 | ((c >> 6) & 0x3F)));
+ impl._putByte(buffer.writeIndex() + 2, (byte) (0x80 | ((c >> 0) & 0x3F)));
+ impl.advanceWrite(3);
+ } else {
+ impl._putByte(buffer.writeIndex(), (byte) (0xC0 | ((c >> 6) & 0x1F)));
+ impl._putByte(buffer.writeIndex() + 1, (byte) (0x80 | ((c >> 0) & 0x3F)));
+ impl.advanceWrite(2);
+ }
+ }
+ }
+
+ @Override
+ public String read(@NotNull NetworkBuffer buffer) {
+ int utflen = buffer.read(UNSIGNED_SHORT);
+ if (buffer.readableBytes() < utflen) throw new IllegalArgumentException("Invalid String size.");
+ byte[] bytearr = buffer.read(RAW_BYTES);
+ final char[] chararr = new char[utflen];
+
+ int c, char2, char3;
+ int count = 0;
+ int chararr_count = 0;
+
+ while (count < utflen) {
+ c = (int) bytearr[count] & 0xff;
+ if (c > 127) break;
+ count++;
+ chararr[chararr_count++] = (char) c;
+ }
+
+ while (count < utflen) {
+ c = (int) bytearr[count] & 0xff;
+ try { // Surround in try catch to throw a runtime exception instead of a checked one
+ switch (c >> 4) {
+ case 0, 1, 2, 3, 4, 5, 6, 7 -> {
+ /* 0xxxxxxx*/
+ count++;
+ chararr[chararr_count++] = (char) c;
+ }
+ case 12, 13 -> {
+ /* 110x xxxx 10xx xxxx*/
+ count += 2;
+ if (count > utflen)
+ throw new UTFDataFormatException(
+ "malformed input: partial character at end");
+ char2 = bytearr[count - 1];
+ if ((char2 & 0xC0) != 0x80)
+ throw new UTFDataFormatException(
+ "malformed input around byte " + count);
+ chararr[chararr_count++] = (char) (((c & 0x1F) << 6) |
+ (char2 & 0x3F));
+ }
+ case 14 -> {
+ /* 1110 xxxx 10xx xxxx 10xx xxxx */
+ count += 3;
+ if (count > utflen)
+ throw new UTFDataFormatException(
+ "malformed input: partial character at end");
+ char2 = bytearr[count - 2];
+ char3 = bytearr[count - 1];
+ if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
+ throw new UTFDataFormatException(
+ "malformed input around byte " + (count - 1));
+ chararr[chararr_count++] = (char) (((c & 0x0F) << 12) |
+ ((char2 & 0x3F) << 6) |
+ ((char3 & 0x3F) << 0));
+ }
+ default ->
+ /* 10xx xxxx, 1111 xxxx */
+ throw new UTFDataFormatException(
+ "malformed input around byte " + count);
+ }
+ } catch (UTFDataFormatException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+ // The number of chars produced may be less than utflen
+ return new String(chararr, 0, chararr_count);
+ }
+ }
+
static long sizeOf(Type type, T value, Registries registries) {
NetworkBuffer buffer = NetworkBufferImpl.dummy(registries);
type.write(buffer, value);
diff --git a/src/main/java/net/minestom/server/network/packet/PacketWriting.java b/src/main/java/net/minestom/server/network/packet/PacketWriting.java
index d93e38694..8736388d9 100644
--- a/src/main/java/net/minestom/server/network/packet/PacketWriting.java
+++ b/src/main/java/net/minestom/server/network/packet/PacketWriting.java
@@ -202,6 +202,7 @@ public final class PacketWriting {
if (written < minWrite) {
// Try again with a bigger buffer
final long newSize = Math.min(buffer.capacity() * 2, ServerFlag.MAX_PACKET_SIZE);
+ if (newSize == buffer.capacity()) break; // We reached the maximum size
buffer.resize(newSize);
} else {
// At least one packet has been written
diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoUpdatePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoUpdatePacket.java
index 8c4256516..239ae76cc 100644
--- a/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoUpdatePacket.java
+++ b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoUpdatePacket.java
@@ -10,16 +10,15 @@ import net.minestom.server.network.player.GameProfile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.UUID;
+import java.util.*;
+import java.util.function.UnaryOperator;
import static net.minestom.server.network.NetworkBuffer.*;
public record PlayerInfoUpdatePacket(
@NotNull EnumSet<@NotNull Action> actions,
@NotNull List<@NotNull Entry> entries
-) implements ServerPacket.Play {
+) implements ServerPacket.Play, ServerPacket.ComponentHolding {
public static final int MAX_ENTRIES = 1024;
public PlayerInfoUpdatePacket(@NotNull Action action, @NotNull Entry entry) {
@@ -46,6 +45,33 @@ public record PlayerInfoUpdatePacket(
}
};
+ @Override
+ public @NotNull Collection components() {
+ final List components = new ArrayList<>();
+ for (final Entry entry : entries) {
+ if (entry.displayName() == null) continue;
+ components.add(entry.displayName());
+ }
+ return components;
+ }
+
+ @Override
+ public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) {
+ final List newEntries = new ArrayList<>();
+ for (final Entry entry : entries) {
+ final Component displayName = entry.displayName();
+ if (displayName != null) {
+ newEntries.add(new Entry(entry.uuid, entry.username,
+ entry.properties, entry.listed, entry.latency,
+ entry.gameMode, operator.apply(displayName),
+ entry.chatSession, entry.listOrder));
+ } else {
+ newEntries.add(entry);
+ }
+ }
+ return new PlayerInfoUpdatePacket(actions, newEntries);
+ }
+
public record Entry(UUID uuid, String username, List properties,
boolean listed, int latency, GameMode gameMode,
@Nullable Component displayName, @Nullable ChatSession chatSession,
diff --git a/src/main/java/net/minestom/server/network/player/PlayerConnection.java b/src/main/java/net/minestom/server/network/player/PlayerConnection.java
index 1b3c2682a..f09a04027 100644
--- a/src/main/java/net/minestom/server/network/player/PlayerConnection.java
+++ b/src/main/java/net/minestom/server/network/player/PlayerConnection.java
@@ -141,9 +141,9 @@ public abstract class PlayerConnection {
*/
public void disconnect() {
this.online = false;
- MinecraftServer.getConnectionManager().removePlayer(this);
- final Player player = getPlayer();
+ final Player player = MinecraftServer.getConnectionManager().getPlayer(this);
if (player != null) {
+ MinecraftServer.getConnectionManager().removePlayer(this);
if (connectionState == ConnectionState.PLAY && !player.isRemoved())
player.scheduleNextTick(Entity::remove);
else {
diff --git a/src/main/java/net/minestom/server/registry/Registry.java b/src/main/java/net/minestom/server/registry/Registry.java
index f413861fa..431db24d1 100644
--- a/src/main/java/net/minestom/server/registry/Registry.java
+++ b/src/main/java/net/minestom/server/registry/Registry.java
@@ -137,6 +137,10 @@ public final class Registry {
return new JukeboxSongEntry(namespace, main, null);
}
+ public static GameEventEntry gameEventEntry(String namespace, Properties properties) {
+ return new GameEventEntry(namespace, properties, null);
+ }
+
public static @NotNull InputStream loadRegistryFile(@NotNull Resource resource) throws IOException {
// 1. Try to load from jar resources
InputStream resourceStream = Registry.class.getClassLoader().getResourceAsStream(resource.name);
@@ -245,6 +249,7 @@ public final class Registry {
ENTITY_TYPE_TAGS("tags/entity_type.json"),
FLUID_TAGS("tags/fluid.json"),
GAMEPLAY_TAGS("tags/game_event.json"),
+ GAME_EVENTS("game_events.json"),
ITEM_TAGS("tags/item.json"),
ENCHANTMENT_TAGS("tags/enchantment.json"),
BIOME_TAGS("tags/biome.json"),
@@ -270,6 +275,12 @@ public final class Registry {
}
}
+ public record GameEventEntry(NamespaceID namespace, Properties main, Properties custom) implements Entry {
+ public GameEventEntry(String namespace, Properties main, Properties custom) {
+ this(NamespaceID.from(namespace), main, custom);
+ }
+ }
+
public static final class BlockEntry implements Entry {
private final NamespaceID namespace;
private final int id;
diff --git a/src/main/java/net/minestom/server/scoreboard/TeamManager.java b/src/main/java/net/minestom/server/scoreboard/TeamManager.java
index ecca0be73..f1a9721d5 100644
--- a/src/main/java/net/minestom/server/scoreboard/TeamManager.java
+++ b/src/main/java/net/minestom/server/scoreboard/TeamManager.java
@@ -5,7 +5,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.entity.LivingEntity;
import net.minestom.server.entity.Player;
import net.minestom.server.utils.PacketSendingUtils;
-import net.minestom.server.utils.UniqueIdUtils;
+import net.minestom.server.utils.UUIDUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@@ -158,7 +158,7 @@ public final class TeamManager {
public List getPlayers(Team team) {
List players = new ArrayList<>();
for (String member : team.getMembers()) {
- boolean match = UniqueIdUtils.isUniqueId(member);
+ boolean match = UUIDUtils.isUuid(member);
if (!match) players.add(member);
}
@@ -176,7 +176,7 @@ public final class TeamManager {
public List getEntities(Team team) {
List entities = new ArrayList<>();
for (String member : team.getMembers()) {
- boolean match = UniqueIdUtils.isUniqueId(member);
+ boolean match = UUIDUtils.isUuid(member);
if (match) entities.add(member);
}
diff --git a/src/main/java/net/minestom/server/snapshot/SnapshotImpl.java b/src/main/java/net/minestom/server/snapshot/SnapshotImpl.java
index 02e753b8a..a934813fc 100644
--- a/src/main/java/net/minestom/server/snapshot/SnapshotImpl.java
+++ b/src/main/java/net/minestom/server/snapshot/SnapshotImpl.java
@@ -97,7 +97,7 @@ public final class SnapshotImpl {
final Section section = sections[globalToChunk(y) - minSection];
final int blockStateId = section.blockPalette()
.get(globalToSectionRelative(x), globalToSectionRelative(y), globalToSectionRelative(z));
- return Objects.requireNonNullElse(Block.fromStateId((short) blockStateId), Block.AIR);
+ return Objects.requireNonNullElse(Block.fromStateId(blockStateId), Block.AIR);
}
@Override
diff --git a/src/main/java/net/minestom/server/tag/Serializers.java b/src/main/java/net/minestom/server/tag/Serializers.java
index f26ff1155..9f18715c7 100644
--- a/src/main/java/net/minestom/server/tag/Serializers.java
+++ b/src/main/java/net/minestom/server/tag/Serializers.java
@@ -5,7 +5,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.ServerFlag;
import net.minestom.server.item.ItemStack;
-import net.minestom.server.utils.UniqueIdUtils;
+import net.minestom.server.utils.UUIDUtils;
import java.util.function.Function;
@@ -23,7 +23,7 @@ final class Serializers {
static final Entry STRING = new Entry<>(BinaryTagTypes.STRING, StringBinaryTag::value, StringBinaryTag::stringBinaryTag);
static final Entry NBT_ENTRY = new Entry<>(null, Function.identity(), Function.identity());
- static final Entry UUID = new Entry<>(BinaryTagTypes.INT_ARRAY, UniqueIdUtils::fromNbt, UniqueIdUtils::toNbt);
+ static final Entry UUID = new Entry<>(BinaryTagTypes.INT_ARRAY, UUIDUtils::fromNbt, UUIDUtils::toNbt);
static final Entry ITEM = new Entry<>(BinaryTagTypes.COMPOUND, ItemStack::fromItemNBT, ItemStack::toItemNBT);
static final Entry COMPONENT = new Entry<>(BinaryTagTypes.STRING, input -> GsonComponentSerializer.gson().deserialize(input.value()),
component -> StringBinaryTag.stringBinaryTag(GsonComponentSerializer.gson().serialize(component)));
diff --git a/src/main/java/net/minestom/server/utils/UniqueIdUtils.java b/src/main/java/net/minestom/server/utils/UUIDUtils.java
similarity index 91%
rename from src/main/java/net/minestom/server/utils/UniqueIdUtils.java
rename to src/main/java/net/minestom/server/utils/UUIDUtils.java
index 8d04807af..9d9717838 100644
--- a/src/main/java/net/minestom/server/utils/UniqueIdUtils.java
+++ b/src/main/java/net/minestom/server/utils/UUIDUtils.java
@@ -11,7 +11,7 @@ import java.util.regex.Pattern;
* An utilities class for {@link UUID}.
*/
@ApiStatus.Internal
-public final class UniqueIdUtils {
+public final class UUIDUtils {
public static final Pattern UNIQUE_ID_PATTERN = Pattern.compile("\\b[0-9a-f]{8}\\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\\b[0-9a-f]{12}\\b");
/**
@@ -20,8 +20,8 @@ public final class UniqueIdUtils {
* @param input The input string to be checked
* @return {@code true} if the input an unique identifier, otherwise {@code false}
*/
- public static boolean isUniqueId(String input) {
- return input.matches(UNIQUE_ID_PATTERN.pattern());
+ public static boolean isUuid(String input) {
+ return UNIQUE_ID_PATTERN.matcher(input).matches();
}
public static @NotNull UUID fromNbt(@NotNull IntArrayBinaryTag tag) {
diff --git a/src/main/java/net/minestom/server/utils/chunk/ChunkUpdateLimitChecker.java b/src/main/java/net/minestom/server/utils/chunk/ChunkUpdateLimitChecker.java
index bff217aa6..e6eec0758 100644
--- a/src/main/java/net/minestom/server/utils/chunk/ChunkUpdateLimitChecker.java
+++ b/src/main/java/net/minestom/server/utils/chunk/ChunkUpdateLimitChecker.java
@@ -6,17 +6,28 @@ import org.jetbrains.annotations.ApiStatus;
import java.util.Arrays;
+/**
+ * Allows to limit operations with recently operated chunks
+ *
+ * {@link ChunkUpdateLimitChecker#historySize} defines how many last chunks will be remembered
+ * to skip operations with them via {@link ChunkUpdateLimitChecker#addToHistory(Chunk)} returning {@code false}
+ */
@ApiStatus.Internal
public final class ChunkUpdateLimitChecker {
+
private final int historySize;
private final long[] chunkHistory;
public ChunkUpdateLimitChecker(int historySize) {
- this.historySize = historySize;
- this.chunkHistory = new long[historySize];
+ this.historySize = Math.max(0, historySize);
+ this.chunkHistory = new long[this.historySize];
this.clearHistory();
}
+ public boolean isEnabled() {
+ return historySize > 0;
+ }
+
/**
* Adds the chunk to the history
*
@@ -24,14 +35,19 @@ public final class ChunkUpdateLimitChecker {
* @return {@code true} if it's a new chunk in the history
*/
public boolean addToHistory(Chunk chunk) {
+ if (!isEnabled()) {
+ return true;
+ }
final long index = CoordConversion.chunkIndex(chunk.getChunkX(), chunk.getChunkZ());
boolean result = true;
final int lastIndex = historySize - 1;
- for (int i = 0; i < lastIndex; i++) {
+ for (int i = 0; i <= lastIndex; i++) {
if (chunkHistory[i] == index) {
result = false;
}
- chunkHistory[i] = chunkHistory[i + 1];
+ if (i != lastIndex) {
+ chunkHistory[i] = chunkHistory[i + 1];
+ }
}
chunkHistory[lastIndex] = index;
return result;
diff --git a/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java b/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java
index b00918286..90f028fbd 100644
--- a/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java
+++ b/src/main/java/net/minestom/server/utils/nbt/BinaryTagSerializer.java
@@ -14,7 +14,7 @@ import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.NamespaceID;
-import net.minestom.server.utils.UniqueIdUtils;
+import net.minestom.server.utils.UUIDUtils;
import net.minestom.server.utils.Unit;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.ApiStatus;
@@ -257,7 +257,7 @@ public interface BinaryTagSerializer {
BinaryTagSerializer UUID = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(java.util.@NotNull UUID value) {
- return UniqueIdUtils.toNbt(value);
+ return UUIDUtils.toNbt(value);
}
@Override
@@ -265,7 +265,7 @@ public interface BinaryTagSerializer {
if (!(tag instanceof IntArrayBinaryTag intArrayTag)) {
throw new IllegalArgumentException("unexpected uuid type: " + tag.type());
}
- return UniqueIdUtils.fromNbt(intArrayTag);
+ return UUIDUtils.fromNbt(intArrayTag);
}
};
diff --git a/src/test/java/net/minestom/server/entity/EntityTeleportIntegrationTest.java b/src/test/java/net/minestom/server/entity/EntityTeleportIntegrationTest.java
index eaa513b9d..ef26d4dbb 100644
--- a/src/test/java/net/minestom/server/entity/EntityTeleportIntegrationTest.java
+++ b/src/test/java/net/minestom/server/entity/EntityTeleportIntegrationTest.java
@@ -8,6 +8,11 @@ import net.minestom.testing.Env;
import net.minestom.testing.EnvTest;
import org.junit.jupiter.api.Test;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
@EnvTest
@@ -99,4 +104,21 @@ public class EntityTeleportIntegrationTest {
player.teleport(new Pos(5, 10, 2, 5, 5), null, RelativeFlags.VIEW).join();
assertEquals(player.getPosition(), new Pos(5, 10, 2, 95, 5));
}
+
+ @Test
+ public void entityTeleportToInfinity(Env env) throws ExecutionException, InterruptedException, TimeoutException {
+ var instance = env.createFlatInstance();
+ var entity = new Entity(EntityTypes.ZOMBIE);
+ entity.setInstance(instance, new Pos(0, 42, 0)).join();
+ assertEquals(instance, entity.getInstance());
+ assertEquals(new Pos(0, 42, 0), entity.getPosition());
+
+ entity.teleport(new Pos(Double.POSITIVE_INFINITY, 42, 52)).join();
+ CompletableFuture.runAsync(() -> entity.tick(System.currentTimeMillis()))
+ .get(10, TimeUnit.SECONDS);
+ // This should not hang forever
+
+ // The position should have been capped at 2 billion.
+ assertEquals(new Pos(2_000_000_000, 42, 52), entity.getPosition());
+ }
}
diff --git a/src/test/java/net/minestom/server/event/EventNodeTest.java b/src/test/java/net/minestom/server/event/EventNodeTest.java
index 114033a08..3fa1a782a 100644
--- a/src/test/java/net/minestom/server/event/EventNodeTest.java
+++ b/src/test/java/net/minestom/server/event/EventNodeTest.java
@@ -124,6 +124,33 @@ public class EventNodeTest {
assertTrue(result1.get(), "Recursive1 should be called due to the RecursiveEvent interface");
}
+ @Test
+ public void testRecursiveChild() {
+ var called1 = new AtomicBoolean(false);
+ var called2 = new AtomicBoolean(false);
+ var child1 = EventNode.all("child1");
+ var child2 = EventNode.all("child2");
+ child1.addListener(Recursive1.class, event -> called1.set(true));
+ child2.addListener(Recursive1.class, event -> called2.set(true));
+
+ var node = EventNode.all("main");
+ node.addChild(child1);
+
+ node.call(new Recursive2());
+
+ assertTrue(called1.get());
+ assertFalse(called2.get());
+ called1.set(false);
+
+ node.removeChild(child1);
+ node.addChild(child2);
+
+ node.call(new Recursive2());
+
+ assertFalse(called1.get());
+ assertTrue(called2.get());
+ }
+
// FIXME: nodes are currently unable to retrieve sub handles
//@Test
//public void recursiveSuper() {
diff --git a/src/test/java/net/minestom/server/instance/InstanceUnregisterIntegrationTest.java b/src/test/java/net/minestom/server/instance/InstanceUnregisterIntegrationTest.java
index 45e43c555..c397507bd 100644
--- a/src/test/java/net/minestom/server/instance/InstanceUnregisterIntegrationTest.java
+++ b/src/test/java/net/minestom/server/instance/InstanceUnregisterIntegrationTest.java
@@ -99,7 +99,7 @@ public class InstanceUnregisterIntegrationTest {
private void tmp(InstanceContainer instanceContainer) {
instanceContainer.eventNode().addListener(InstanceTickEvent.class, (e) -> {
- var uuid = instanceContainer.getUniqueId();
+ var uuid = instanceContainer.getUuid();
});
}
}
diff --git a/src/test/java/net/minestom/server/instance/palette/PaletteIndirectTest.java b/src/test/java/net/minestom/server/instance/palette/PaletteIndirectTest.java
new file mode 100644
index 000000000..50d6e7571
--- /dev/null
+++ b/src/test/java/net/minestom/server/instance/palette/PaletteIndirectTest.java
@@ -0,0 +1,17 @@
+package net.minestom.server.instance.palette;
+
+import org.junit.jupiter.api.Test;
+
+public class PaletteIndirectTest {
+
+ @Test
+ public void constructor() {
+ var palette = new PaletteIndirect(16, 8, (byte) 4);
+ palette.set(0, 0, 1, 1);
+ var otherPalette = new PaletteIndirect(palette.dimension(), palette.maxBitsPerEntry(), (byte) palette.bitsPerEntry(), palette.count(), palette.paletteToValueList.toIntArray(), palette.values);
+
+ palette.getAll((x, y, z, value) -> {
+ assert value == otherPalette.get(x, y, z);
+ });
+ }
+}
diff --git a/src/test/java/net/minestom/server/item/component/UnitTest.java b/src/test/java/net/minestom/server/item/component/UnitTest.java
index e0a5f7e8c..78ac53e6c 100644
--- a/src/test/java/net/minestom/server/item/component/UnitTest.java
+++ b/src/test/java/net/minestom/server/item/component/UnitTest.java
@@ -1,5 +1,6 @@
package net.minestom.server.item.component;
+import net.minestom.server.MinecraftServer;
import net.minestom.server.component.DataComponent;
import net.minestom.server.item.ItemComponent;
import net.minestom.server.network.NetworkBuffer;
@@ -25,6 +26,10 @@ public class UnitTest extends AbstractItemComponentTest {
ItemComponent.GLIDER
);
+ static {
+ MinecraftServer.init();
+ }
+
@Override
protected @NotNull DataComponent component() {
return UNIT_COMPONENTS.getFirst();
@@ -46,7 +51,7 @@ public class UnitTest extends AbstractItemComponentTest {
// Try to write as a Unit and if it fails we can ignore that type
try {
//noinspection unchecked
- ((DataComponent) component).write(NetworkBuffer.resizableBuffer(), Unit.INSTANCE);
+ ((DataComponent) component).write(NetworkBuffer.resizableBuffer(MinecraftServer.process()), Unit.INSTANCE);
} catch (ClassCastException ignored) {
continue;
}
diff --git a/src/test/java/net/minestom/server/listener/UseEntityListenerTest.java b/src/test/java/net/minestom/server/listener/UseEntityListenerTest.java
new file mode 100644
index 000000000..8ddebdeb3
--- /dev/null
+++ b/src/test/java/net/minestom/server/listener/UseEntityListenerTest.java
@@ -0,0 +1,106 @@
+package net.minestom.server.listener;
+
+import net.minestom.server.coordinate.Pos;
+import net.minestom.server.entity.Entity;
+import net.minestom.server.entity.EntityType;
+import net.minestom.server.entity.Player;
+import net.minestom.server.entity.PlayerHand;
+import net.minestom.server.entity.attribute.Attribute;
+import net.minestom.server.event.player.PlayerEntityInteractEvent;
+import net.minestom.server.instance.Instance;
+import net.minestom.server.network.packet.client.play.ClientInteractEntityPacket;
+import net.minestom.testing.Env;
+import net.minestom.testing.EnvTest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@EnvTest
+public class UseEntityListenerTest {
+
+ private Player player;
+ private Entity targetEntity;
+ private boolean eventWasCalled;
+
+ @BeforeEach
+ public void setup(Env env) {
+ Instance instance = env.createFlatInstance();
+
+ player = env.createPlayer(instance, new Pos(0, 0, 0));
+ player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(5.0);
+
+ targetEntity = new Entity(EntityType.SLIME);
+ targetEntity.setInstance(instance, new Pos(2, 0, 2)).join();
+
+ eventWasCalled = false;
+
+ player.eventNode().addListener(PlayerEntityInteractEvent.class, event -> {
+ if (event.getPlayer().equals(player) && event.getTarget().equals(targetEntity)) {
+ eventWasCalled = true;
+ }
+ });
+ }
+
+ @Test
+ public void testInteractionWithinRange() {
+ ClientInteractEntityPacket packet = new ClientInteractEntityPacket(
+ targetEntity.getEntityId(),
+ new ClientInteractEntityPacket.InteractAt(0, 0, 0, PlayerHand.MAIN),
+ false
+ );
+
+ UseEntityListener.useEntityListener(packet, player);
+ assertTrue(eventWasCalled, "Expected PlayerEntityInteractEvent to be called for nearby target");
+ }
+
+ @Test
+ public void testInteractionOutOfRange() {
+ player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(1.0);
+
+ targetEntity.teleport(new Pos(10, 0, 10)).join();
+ ClientInteractEntityPacket packet = new ClientInteractEntityPacket(
+ targetEntity.getEntityId(),
+ new ClientInteractEntityPacket.InteractAt(0, 0, 0, PlayerHand.MAIN),
+ false
+ );
+
+ eventWasCalled = false;
+ UseEntityListener.useEntityListener(packet, player);
+ assertFalse(eventWasCalled, "Expected PlayerEntityInteractEvent NOT to be called for out-of-range target");
+ }
+
+ @Test
+ public void testInteractionConsideringHitboxAndEyePosition() {
+ player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(1.5);
+
+ targetEntity.teleport(new Pos(1.6, 0, 0)).join();
+
+ ClientInteractEntityPacket packet = new ClientInteractEntityPacket(
+ targetEntity.getEntityId(),
+ new ClientInteractEntityPacket.InteractAt(0, 0, 0, PlayerHand.MAIN),
+ false
+ );
+
+ eventWasCalled = false;
+ UseEntityListener.useEntityListener(packet, player);
+ assertTrue(eventWasCalled, "Expected PlayerEntityInteractEvent to be called considering hitbox size and eye position");
+ }
+
+
+ @Test
+ public void testInteractionConsideringEyeHeight() {
+ player.teleport(new Pos(0, 1.6, 0)).join();
+ targetEntity.teleport(new Pos(0, 1.6, 2)).join();
+
+ ClientInteractEntityPacket packet = new ClientInteractEntityPacket(
+ targetEntity.getEntityId(),
+ new ClientInteractEntityPacket.InteractAt(0, 0, 0, PlayerHand.MAIN),
+ false
+ );
+
+ eventWasCalled = false;
+ UseEntityListener.useEntityListener(packet, player);
+ assertTrue(eventWasCalled, "Expected PlayerEntityInteractEvent to be called considering eye height");
+ }
+}
diff --git a/src/test/java/net/minestom/server/network/NetworkBufferTest.java b/src/test/java/net/minestom/server/network/NetworkBufferTest.java
index 8d782630e..72f2b5fdc 100644
--- a/src/test/java/net/minestom/server/network/NetworkBufferTest.java
+++ b/src/test/java/net/minestom/server/network/NetworkBufferTest.java
@@ -11,6 +11,8 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.junit.jupiter.api.Test;
+import java.io.IOException;
+import java.io.UTFDataFormatException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Consumer;
@@ -474,6 +476,62 @@ public class NetworkBufferTest {
assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING)); // oom
}
+ @Test
+ public void oomStringUtf8Regression() {
+ var buffer = NetworkBuffer.resizableBuffer(100);
+ buffer.write(UNSIGNED_SHORT, 65535); // String length
+ buffer.write(RAW_BYTES, "Hello".getBytes(StandardCharsets.UTF_8)); // String data
+
+ assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING)); // oom
+ }
+
+ @Test
+ public void testStringUtf8ModifiedWrite() throws IOException {
+ var stream = new java.io.ByteArrayOutputStream();
+ java.io.DataOutputStream out = new java.io.DataOutputStream(stream);
+ out.writeUTF("Hello");
+
+ assertBufferType(STRING_IO_UTF8, "Hello", stream.toByteArray());
+ }
+
+
+ @Test
+ public void testStringUtf8ModifiedRead() throws IOException {
+ var stream = new java.io.ByteArrayOutputStream();
+ java.io.DataOutputStream out = new java.io.DataOutputStream(stream);
+ out.writeUTF("Hello");
+ var buffer = NetworkBuffer.wrap(stream.toByteArray(), 0, stream.size());
+ assertEquals("Hello", buffer.read(STRING_IO_UTF8));
+ }
+
+ @Test
+ public void oomStringUtf8ModfiedRegression() throws IOException {
+ var buffer = NetworkBuffer.resizableBuffer(100);
+ buffer.write(UNSIGNED_SHORT, 65535); // String length
+ // Write the raw bytes that are invalid
+ buffer.write(RAW_BYTES, new byte[]{(byte) 0xC0, (byte) 0x80}); // Invalid UTF-8
+
+ assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING_IO_UTF8)); // oom
+ buffer.clear();
+
+ var stream = new java.io.ByteArrayOutputStream();
+ java.io.DataOutputStream out = new java.io.DataOutputStream(stream);
+ out.writeUTF("Hello");
+ var byteArray = stream.toByteArray();
+
+ // Mess with the length to 0
+ byteArray[0] = (byte) 0x00;
+ byteArray[1] = (byte) 0x00;
+
+ assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING_IO_UTF8)); // oom
+
+ buffer.clear();
+ buffer.write(UNSIGNED_SHORT, 5);
+ buffer.write(RAW_BYTES, new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); // Invalid utf8
+
+ assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING_IO_UTF8)); // oom
+ }
+
static void assertBufferType(NetworkBuffer.@NotNull Type type, @UnknownNullability T value, byte[] expected, @NotNull Action action) {
var buffer = NetworkBuffer.resizableBuffer(MinecraftServer.process());
action.write(buffer, type, value);
diff --git a/src/test/java/net/minestom/server/utils/UUIDUtilsTest.java b/src/test/java/net/minestom/server/utils/UUIDUtilsTest.java
new file mode 100644
index 000000000..a249954e6
--- /dev/null
+++ b/src/test/java/net/minestom/server/utils/UUIDUtilsTest.java
@@ -0,0 +1,34 @@
+package net.minestom.server.utils;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class UUIDUtilsTest {
+ private static final UUID TEST_UUID = UUID.fromString("d2ac7139-76a6-435b-b659-7852d34dd7a3");
+ private static final int[] TEST_INT_ARRAY = new int[]{
+ 0xd2ac7139,
+ 0x76a6435b,
+ 0xb6597852,
+ 0xd34dd7a3
+ };
+
+ @Test
+ void isUuid() {
+ assertTrue(UUIDUtils.isUuid("d2ac7139-76a6-435b-b659-7852d34dd7a3"));
+ assertFalse(UUIDUtils.isUuid("This is not a UUID"));
+ assertFalse(UUIDUtils.isUuid("d2acL139-76a6-435b-b659-7852d34dd7a3"));
+ }
+
+ @Test
+ void uuidToIntArray() {
+ assertArrayEquals(TEST_INT_ARRAY, UUIDUtils.uuidToIntArray(TEST_UUID));
+ }
+
+ @Test
+ void intArrayToUuid() {
+ assertEquals(TEST_UUID, UUIDUtils.intArrayToUuid(TEST_INT_ARRAY));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/net/minestom/server/utils/chunk/ChunkUpdateLimitCheckerTest.java b/src/test/java/net/minestom/server/utils/chunk/ChunkUpdateLimitCheckerTest.java
new file mode 100644
index 000000000..32266eea2
--- /dev/null
+++ b/src/test/java/net/minestom/server/utils/chunk/ChunkUpdateLimitCheckerTest.java
@@ -0,0 +1,54 @@
+package net.minestom.server.utils.chunk;
+
+import net.minestom.server.instance.DynamicChunk;
+import net.minestom.testing.Env;
+import net.minestom.testing.EnvTest;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@EnvTest
+public class ChunkUpdateLimitCheckerTest {
+
+ @Test
+ public void testHistory(Env env) {
+ var instance = env.createFlatInstance();
+ var limiter = new ChunkUpdateLimitChecker(3);
+
+ assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
+ assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 1)));
+ assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 2)));
+ // history : 0, 1, 2
+
+ assertFalse(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
+ // history : 1, 2, 0
+ assertFalse(limiter.addToHistory(new DynamicChunk(instance, 0, 1)));
+ // history : 2, 0, 1
+ assertFalse(limiter.addToHistory(new DynamicChunk(instance, 0, 2)));
+ // history : 0, 1, 2
+
+ assertFalse(limiter.addToHistory(new DynamicChunk(instance, 0, 2)));
+ // history : 1, 2, 2
+ assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
+ }
+
+ @Test
+ public void testOneSlotHistory(Env env) {
+ var instance = env.createFlatInstance();
+ var limiter = new ChunkUpdateLimitChecker(1);
+ assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
+ assertFalse(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
+ assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 1)));
+ assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
+ }
+
+ @Test
+ public void testDisabling(Env env) {
+ var instance = env.createFlatInstance();
+ var limiter = new ChunkUpdateLimitChecker(0);
+ assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
+ assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
+ assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 1)));
+ }
+}