mirror of
https://github.com/Minestom/Minestom.git
synced 2025-02-11 09:51:35 +01:00
Merge branch 'master' into fix-movement-simulation
This commit is contained in:
commit
6211acf9d3
61
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
61
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@ -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
|
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -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
|
34
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
34
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
@ -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
|
41
.github/ISSUE_TEMPLATE/performance.yml
vendored
Normal file
41
.github/ISSUE_TEMPLATE/performance.yml
vendored
Normal file
@ -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
|
31
.github/old-workflows/check-pr-style.yml
vendored
31
.github/old-workflows/check-pr-style.yml
vendored
@ -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"
|
|
67
.github/old-workflows/codeql-analysis.yml
vendored
67
.github/old-workflows/codeql-analysis.yml
vendored
@ -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
|
|
29
.github/pull_request_template.md
vendored
Normal file
29
.github/pull_request_template.md
vendored
Normal file
@ -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...
|
43
.github/workflows/build-pr.yml
vendored
Normal file
43
.github/workflows/build-pr.yml
vendored
Normal file
@ -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
|
41
.github/workflows/build.yml
vendored
Normal file
41
.github/workflows/build.yml
vendored
Normal file
@ -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 }}
|
1
.github/workflows/close_invalid_prs.yml
vendored
1
.github/workflows/close_invalid_prs.yml
vendored
@ -7,6 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run:
|
run:
|
||||||
|
name: Close invalid PRs
|
||||||
if: |
|
if: |
|
||||||
github.repository != github.event.pull_request.head.repo.full_name &&
|
github.repository != github.event.pull_request.head.repo.full_name &&
|
||||||
(
|
(
|
||||||
|
3
.github/workflows/javadoc.yml
vendored
3
.github/workflows/javadoc.yml
vendored
@ -6,9 +6,8 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
name: Build and deploy Javadoc
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK 21
|
- name: Set up JDK 21
|
||||||
|
59
.github/workflows/pr.yml
vendored
59
.github/workflows/pr.yml
vendored
@ -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
|
|
32
.github/workflows/snapshot-deploy.yaml
vendored
32
.github/workflows/snapshot-deploy.yaml
vendored
@ -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
|
|
12
.github/workflows/trigger-jitpack-build.yml
vendored
12
.github/workflows/trigger-jitpack-build.yml
vendored
@ -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"
|
|
@ -52,6 +52,7 @@ public class Generators {
|
|||||||
generator.generate(resource("attributes.json"), "net.minestom.server.entity.attribute", "Attribute", "AttributeImpl", "Attributes");
|
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("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("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
|
// Dynamic registries
|
||||||
|
@ -141,7 +141,6 @@ public class PlayerInit {
|
|||||||
inventory.addItemStack(getFoodItem(10000));
|
inventory.addItemStack(getFoodItem(10000));
|
||||||
inventory.addItemStack(getFoodItem(Integer.MAX_VALUE));
|
inventory.addItemStack(getFoodItem(Integer.MAX_VALUE));
|
||||||
|
|
||||||
|
|
||||||
if (event.isFirstSpawn()) {
|
if (event.isFirstSpawn()) {
|
||||||
event.getPlayer().sendNotification(new Notification(
|
event.getPlayer().sendNotification(new Notification(
|
||||||
Component.text("Welcome!"),
|
Component.text("Welcome!"),
|
||||||
|
127
src/autogenerated/java/net/minestom/server/game/GameEvents.java
Normal file
127
src/autogenerated/java/net/minestom/server/game/GameEvents.java
Normal file
@ -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");
|
||||||
|
}
|
@ -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 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_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 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
|
// Network buffers
|
||||||
public static final int MAX_PACKET_SIZE = intProperty("minestom.max-packet-size", 2_097_151); // 3 bytes var-int
|
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);
|
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) {
|
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) {
|
private static long longProperty(String name, long defaultValue) {
|
||||||
|
@ -5,6 +5,7 @@ import net.minestom.server.coordinate.Pos;
|
|||||||
import net.minestom.server.coordinate.Vec;
|
import net.minestom.server.coordinate.Vec;
|
||||||
import net.minestom.server.entity.EntityPose;
|
import net.minestom.server.entity.EntityPose;
|
||||||
import net.minestom.server.instance.block.BlockFace;
|
import net.minestom.server.instance.block.BlockFace;
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@ -21,11 +22,11 @@ public record BoundingBox(Vec relativeStart, Vec relativeEnd) implements Shape {
|
|||||||
final static BoundingBox ZERO = new BoundingBox(Vec.ZERO, Vec.ZERO);
|
final static BoundingBox ZERO = new BoundingBox(Vec.ZERO, Vec.ZERO);
|
||||||
|
|
||||||
public BoundingBox(double width, double height, double depth, Point offset) {
|
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) {
|
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
|
@Override
|
||||||
@ -94,6 +95,41 @@ public record BoundingBox(Vec relativeStart, Vec relativeEnd) implements Shape {
|
|||||||
return new BoundingBox(width(), height(), depth(), offset);
|
return new BoundingBox(width(), height(), depth(), offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link BoundingBox} with an expanded size from its center in every plane.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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() {
|
public double width() {
|
||||||
return relativeEnd.x() - relativeStart.x();
|
return relativeEnd.x() - relativeStart.x();
|
||||||
}
|
}
|
||||||
|
@ -132,8 +132,8 @@ final class CommandParserImpl implements CommandParser {
|
|||||||
int start = reader.cursor();
|
int start = reader.cursor();
|
||||||
|
|
||||||
if (reader.hasRemaining()) {
|
if (reader.hasRemaining()) {
|
||||||
ArgumentResult<?> result = parseArgument(sender, argument, reader);
|
|
||||||
SuggestionCallback suggestionCallback = argument.getSuggestionCallback();
|
SuggestionCallback suggestionCallback = argument.getSuggestionCallback();
|
||||||
|
ArgumentResult<?> result = parseArgument(sender, argument, reader);
|
||||||
NodeResult nodeResult = new NodeResult(node, chain, (ArgumentResult<Object>) result, suggestionCallback);
|
NodeResult nodeResult = new NodeResult(node, chain, (ArgumentResult<Object>) result, suggestionCallback);
|
||||||
chain.append(nodeResult);
|
chain.append(nodeResult);
|
||||||
if (suggestionCallback != null) chain.suggestionCallback = suggestionCallback;
|
if (suggestionCallback != null) chain.suggestionCallback = suggestionCallback;
|
||||||
@ -200,12 +200,27 @@ final class CommandParserImpl implements CommandParser {
|
|||||||
|
|
||||||
if (reader.hasRemaining()) {
|
if (reader.hasRemaining()) {
|
||||||
// Trailing data is a syntax error
|
// Trailing data is a syntax error
|
||||||
return new NodeResult(
|
// Can get to here if there's a default executor even if the user is still typing the command
|
||||||
node,
|
// So let's supply the next argument's suggestion callback if it exists
|
||||||
|
Node returnNode = node;
|
||||||
|
SuggestionCallback suggestionCallback = argument.getSuggestionCallback();
|
||||||
|
List<Node> nextNodes = node.next();
|
||||||
|
if (!nextNodes.isEmpty()) {
|
||||||
|
returnNode = nextNodes.getFirst();
|
||||||
|
suggestionCallback = returnNode.argument().getSuggestionCallback();
|
||||||
|
}
|
||||||
|
NodeResult nodeResult = new NodeResult(
|
||||||
|
returnNode,
|
||||||
chain,
|
chain,
|
||||||
new ArgumentResult.SyntaxError<>("Command has trailing data", "", -1),
|
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!
|
// Command was successful!
|
||||||
|
@ -28,6 +28,21 @@ public record BlockVec(double x, double y, double z) implements Point {
|
|||||||
this(point.x(), point.y(), point.z());
|
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
|
@Override
|
||||||
public @NotNull Point withX(@NotNull DoubleUnaryOperator operator) {
|
public @NotNull Point withX(@NotNull DoubleUnaryOperator operator) {
|
||||||
return new Vec(operator.applyAsDouble(x), y, z);
|
return new Vec(operator.applyAsDouble(x), y, z);
|
||||||
|
@ -46,6 +46,7 @@ import net.minestom.server.timer.Schedulable;
|
|||||||
import net.minestom.server.timer.Scheduler;
|
import net.minestom.server.timer.Scheduler;
|
||||||
import net.minestom.server.timer.TaskSchedule;
|
import net.minestom.server.timer.TaskSchedule;
|
||||||
import net.minestom.server.utils.ArrayUtils;
|
import net.minestom.server.utils.ArrayUtils;
|
||||||
|
import net.minestom.server.utils.MathUtils;
|
||||||
import net.minestom.server.utils.PacketViewableUtils;
|
import net.minestom.server.utils.PacketViewableUtils;
|
||||||
import net.minestom.server.utils.async.AsyncUtils;
|
import net.minestom.server.utils.async.AsyncUtils;
|
||||||
import net.minestom.server.utils.block.BlockIterator;
|
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<EntityEvent>, Taggable,
|
public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, EventHandler<EntityEvent>, Taggable,
|
||||||
HoverEventSource<ShowEntity>, Sound.Emitter, Shape, AcquirableSource<Entity> {
|
HoverEventSource<ShowEntity>, Sound.Emitter, Shape, AcquirableSource<Entity> {
|
||||||
|
// 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();
|
private static final AtomicInteger LAST_ENTITY_ID = new AtomicInteger();
|
||||||
|
|
||||||
// Certain entities should only have their position packets sent during synchronization
|
// 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 Instance instance;
|
||||||
protected Chunk currentChunk;
|
protected Chunk currentChunk;
|
||||||
protected Pos position;
|
protected Pos position; // Should be updated by setPositionInternal only.
|
||||||
protected Pos previousPosition;
|
protected Pos previousPosition;
|
||||||
protected Pos lastSyncedPosition;
|
protected Pos lastSyncedPosition;
|
||||||
protected boolean onGround;
|
protected boolean onGround;
|
||||||
@ -202,6 +207,19 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
this(entityType, UUID.randomUUID());
|
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.
|
* 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 = () -> {
|
final Runnable endCallback = () -> {
|
||||||
this.previousPosition = this.position;
|
this.previousPosition = this.position;
|
||||||
this.position = globalPosition;
|
setPositionInternal(globalPosition);
|
||||||
this.velocity = globalVelocity;
|
this.velocity = globalVelocity;
|
||||||
refreshCoordinate(globalPosition);
|
refreshCoordinate(globalPosition);
|
||||||
if (this instanceof Player player) player.synchronizePositionAfterTeleport(position, velocity, flags, shouldConfirm);
|
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) {
|
public void setView(float yaw, float pitch) {
|
||||||
final Pos currentPosition = this.position;
|
final Pos currentPosition = this.position;
|
||||||
if (currentPosition.sameView(yaw, pitch)) return;
|
if (currentPosition.sameView(yaw, pitch)) return;
|
||||||
this.position = currentPosition.withView(yaw, pitch);
|
setPositionInternal(currentPosition.withView(yaw, pitch));
|
||||||
synchronizeView();
|
synchronizeView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -784,7 +802,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
if (previousInstance != null) removeFromInstance(previousInstance);
|
if (previousInstance != null) removeFromInstance(previousInstance);
|
||||||
|
|
||||||
this.isActive = true;
|
this.isActive = true;
|
||||||
this.position = spawnPosition;
|
setPositionInternal(spawnPosition);
|
||||||
this.previousPosition = spawnPosition;
|
this.previousPosition = spawnPosition;
|
||||||
this.lastSyncedPosition = spawnPosition;
|
this.lastSyncedPosition = spawnPosition;
|
||||||
this.previousPhysicsResult = null;
|
this.previousPhysicsResult = null;
|
||||||
@ -1236,7 +1254,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
final var previousPosition = this.position;
|
final var previousPosition = this.position;
|
||||||
final Pos position = ignoreView ? previousPosition.withCoord(newPosition) : newPosition;
|
final Pos position = ignoreView ? previousPosition.withCoord(newPosition) : newPosition;
|
||||||
if (position.equals(lastSyncedPosition)) return;
|
if (position.equals(lastSyncedPosition)) return;
|
||||||
this.position = position;
|
setPositionInternal(position);
|
||||||
this.previousPosition = previousPosition;
|
this.previousPosition = previousPosition;
|
||||||
if (!position.samePoint(previousPosition)) refreshCoordinate(position);
|
if (!position.samePoint(previousPosition)) refreshCoordinate(position);
|
||||||
if (nextSynchronizationTick <= ticks + 1 || !sendPackets) {
|
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(),
|
final Pos newPassengerPos = oldPassengerPos.withCoord(newPosition.x(),
|
||||||
newPosition.y() + EntityUtils.getPassengerHeightOffset(this, passenger),
|
newPosition.y() + EntityUtils.getPassengerHeightOffset(this, passenger),
|
||||||
newPosition.z());
|
newPosition.z());
|
||||||
passenger.position = newPassengerPos;
|
passenger.setPositionInternal(newPassengerPos);
|
||||||
passenger.previousPosition = oldPassengerPos;
|
passenger.previousPosition = oldPassengerPos;
|
||||||
passenger.refreshCoordinate(newPassengerPos);
|
passenger.refreshCoordinate(newPassengerPos);
|
||||||
}
|
}
|
||||||
@ -1474,7 +1492,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
this.removed = true;
|
this.removed = true;
|
||||||
if (!permanent) {
|
if (!permanent) {
|
||||||
// Reset some state to be ready for re-use
|
// Reset some state to be ready for re-use
|
||||||
this.position = Pos.ZERO;
|
setPositionInternal(Pos.ZERO);
|
||||||
this.previousPosition = Pos.ZERO;
|
this.previousPosition = Pos.ZERO;
|
||||||
this.lastSyncedPosition = Pos.ZERO;
|
this.lastSyncedPosition = Pos.ZERO;
|
||||||
}
|
}
|
||||||
|
@ -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)
|
// Game state (https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol#Game_Event)
|
||||||
private boolean enableRespawnScreen;
|
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
|
// Experience orb pickup
|
||||||
protected Cooldown experiencePickupCooldown = new Cooldown(Duration.of(10, TimeUnit.SERVER_TICK));
|
protected Cooldown experiencePickupCooldown = new Cooldown(Duration.of(10, TimeUnit.SERVER_TICK));
|
||||||
|
@ -58,7 +58,7 @@ public class WolfMeta extends TameableAnimalMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public sealed interface Variant extends ProtocolObject, WolfVariants permits VariantImpl {
|
public sealed interface Variant extends ProtocolObject, WolfVariants permits VariantImpl {
|
||||||
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<Variant>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::wolfVariant);
|
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<Variant>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::wolfVariant, true);
|
||||||
@NotNull BinaryTagSerializer<DynamicRegistry.Key<Variant>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::wolfVariant);
|
@NotNull BinaryTagSerializer<DynamicRegistry.Key<Variant>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::wolfVariant);
|
||||||
|
|
||||||
static @NotNull Variant create(
|
static @NotNull Variant create(
|
||||||
|
@ -79,7 +79,7 @@ public class PaintingMeta extends EntityMeta implements ObjectDataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public sealed interface Variant extends ProtocolObject, PaintingVariants permits VariantImpl {
|
public sealed interface Variant extends ProtocolObject, PaintingVariants permits VariantImpl {
|
||||||
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<Variant>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::paintingVariant);
|
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<Variant>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::paintingVariant, true);
|
||||||
@NotNull BinaryTagSerializer<DynamicRegistry.Key<Variant>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::paintingVariant);
|
@NotNull BinaryTagSerializer<DynamicRegistry.Key<Variant>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::paintingVariant);
|
||||||
|
|
||||||
static @NotNull Variant create(
|
static @NotNull Variant create(
|
||||||
|
@ -95,7 +95,7 @@ public class PNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
@NotNull Type getType() {
|
public @NotNull Type getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,10 +292,21 @@ non-sealed class EventNodeImpl<T extends Event> implements EventNode<T> {
|
|||||||
aClass -> new Handle<>((Class<T>) aClass));
|
aClass -> new Handle<>((Class<T>) aClass));
|
||||||
handle.invalidate();
|
handle.invalidate();
|
||||||
});
|
});
|
||||||
|
invalidateRecursiveSuperclasses(eventClass);
|
||||||
final EventNodeImpl<? super T> parent = this.parent;
|
final EventNodeImpl<? super T> parent = this.parent;
|
||||||
if (parent != null) parent.invalidateEvent(eventClass);
|
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<T> getEntry(Class<? extends T> type) {
|
private ListenerEntry<T> getEntry(Class<? extends T> type) {
|
||||||
return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>());
|
return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>());
|
||||||
}
|
}
|
||||||
|
66
src/main/java/net/minestom/server/game/GameEvent.java
Normal file
66
src/main/java/net/minestom/server/game/GameEvent.java
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
65
src/main/java/net/minestom/server/game/GameEventImpl.java
Normal file
65
src/main/java/net/minestom/server/game/GameEventImpl.java
Normal file
@ -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<GameEvent> 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<GameEvent> 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);
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,8 @@ package net.minestom.server.gamedata.tags;
|
|||||||
|
|
||||||
import net.kyori.adventure.key.Key;
|
import net.kyori.adventure.key.Key;
|
||||||
import net.kyori.adventure.key.Keyed;
|
import net.kyori.adventure.key.Keyed;
|
||||||
import net.minestom.server.MinecraftServer;
|
|
||||||
import net.minestom.server.entity.EntityType;
|
import net.minestom.server.entity.EntityType;
|
||||||
|
import net.minestom.server.game.GameEvent;
|
||||||
import net.minestom.server.instance.block.Block;
|
import net.minestom.server.instance.block.Block;
|
||||||
import net.minestom.server.item.Material;
|
import net.minestom.server.item.Material;
|
||||||
import net.minestom.server.registry.*;
|
import net.minestom.server.registry.*;
|
||||||
@ -14,10 +14,9 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Objects;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a group of items, blocks, fluids, entity types or 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 {
|
public enum BasicType {
|
||||||
BLOCKS("minecraft:block", Registry.Resource.BLOCK_TAGS,
|
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,
|
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,
|
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,
|
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,
|
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
|
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
|
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,
|
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,
|
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 String identifier;
|
||||||
private final Registry.Resource resource;
|
private final Registry.Resource resource;
|
||||||
private final BiFunction<String, Registries, Integer> function;
|
private final BiFunction<String, Registries, Optional<Integer>> function;
|
||||||
|
|
||||||
BasicType(@NotNull String identifier,
|
BasicType(@NotNull String identifier,
|
||||||
@Nullable Registry.Resource resource,
|
@Nullable Registry.Resource resource,
|
||||||
@Nullable BiFunction<String, Registries, Integer> function) {
|
@Nullable BiFunction<String, Registries, Optional<Integer>> function) {
|
||||||
this.identifier = identifier;
|
this.identifier = identifier;
|
||||||
this.resource = resource;
|
this.resource = resource;
|
||||||
this.function = function;
|
this.function = function;
|
||||||
@ -131,7 +133,7 @@ public final class Tag implements ProtocolObject, Keyed {
|
|||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BiFunction<String, Registries, Integer> getFunction() {
|
public BiFunction<String, Registries, Optional<Integer>> getFunction() {
|
||||||
return function;
|
return function;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,8 +30,8 @@ public final class TagManager {
|
|||||||
|
|
||||||
public @Nullable Tag getTag(Tag.BasicType type, String namespace) {
|
public @Nullable Tag getTag(Tag.BasicType type, String namespace) {
|
||||||
final var tags = tagMap.get(type);
|
final var tags = tagMap.get(type);
|
||||||
for (var tag : tags) {
|
for (final var tag : tags) {
|
||||||
if (tag.getName().asString().equals(namespace))
|
if (tag.name().equals(namespace))
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -46,10 +46,10 @@ public final class TagManager {
|
|||||||
for (Map.Entry<Tag.BasicType, List<Tag>> entry : tagMap.entrySet()) {
|
for (Map.Entry<Tag.BasicType, List<Tag>> entry : tagMap.entrySet()) {
|
||||||
final Tag.BasicType type = entry.getKey();
|
final Tag.BasicType type = entry.getKey();
|
||||||
final String registry = type.getIdentifier();
|
final String registry = type.getIdentifier();
|
||||||
List<TagsPacket.Tag> tags = new ArrayList<>();
|
final List<TagsPacket.Tag> tags = new ArrayList<>();
|
||||||
for (Tag tag : entry.getValue()) {
|
for (final Tag tag : entry.getValue()) {
|
||||||
final String identifier = tag.getName().asString();
|
final String identifier = tag.name();
|
||||||
final int[] values = tag.getValues().stream().mapToInt(value -> type.getFunction().apply(value.asString(), registries)).toArray();
|
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));
|
tags.add(new TagsPacket.Tag(identifier, values));
|
||||||
}
|
}
|
||||||
registryList.add(new TagsPacket.Registry(registry, tags));
|
registryList.add(new TagsPacket.Registry(registry, tags));
|
||||||
|
@ -211,7 +211,7 @@ public class DynamicChunk extends Chunk {
|
|||||||
final Section section = getSectionAt(y);
|
final Section section = getSectionAt(y);
|
||||||
final int blockStateId = section.blockPalette()
|
final int blockStateId = section.blockPalette()
|
||||||
.get(globalToSectionRelative(x), globalToSectionRelative(y), globalToSectionRelative(z));
|
.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
|
@Override
|
||||||
|
@ -103,7 +103,7 @@ public abstract class Instance implements Block.Getter, Block.Setter,
|
|||||||
private final ChunkCache blockRetriever = new ChunkCache(this, null, null);
|
private final ChunkCache blockRetriever = new ChunkCache(this, null, null);
|
||||||
|
|
||||||
// the uuid of this instance
|
// the uuid of this instance
|
||||||
protected UUID uniqueId;
|
protected UUID uuid;
|
||||||
|
|
||||||
// instance custom data
|
// instance custom data
|
||||||
protected TagHandler tagHandler = TagHandler.newHandler();
|
protected TagHandler tagHandler = TagHandler.newHandler();
|
||||||
@ -119,31 +119,31 @@ public abstract class Instance implements Block.Getter, Block.Setter,
|
|||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* 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
|
* @param dimensionType the {@link DimensionType} of the instance
|
||||||
*/
|
*/
|
||||||
public Instance(@NotNull UUID uniqueId, @NotNull DynamicRegistry.Key<DimensionType> dimensionType) {
|
public Instance(@NotNull UUID uuid, @NotNull DynamicRegistry.Key<DimensionType> dimensionType) {
|
||||||
this(uniqueId, dimensionType, dimensionType.namespace());
|
this(uuid, dimensionType, dimensionType.namespace());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* 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
|
* @param dimensionType the {@link DimensionType} of the instance
|
||||||
*/
|
*/
|
||||||
public Instance(@NotNull UUID uniqueId, @NotNull DynamicRegistry.Key<DimensionType> dimensionType, @NotNull NamespaceID dimensionName) {
|
public Instance(@NotNull UUID uuid, @NotNull DynamicRegistry.Key<DimensionType> dimensionType, @NotNull NamespaceID dimensionName) {
|
||||||
this(MinecraftServer.getDimensionTypeRegistry(), uniqueId, dimensionType, dimensionName);
|
this(MinecraftServer.getDimensionTypeRegistry(), uuid, dimensionType, dimensionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* 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
|
* @param dimensionType the {@link DimensionType} of the instance
|
||||||
*/
|
*/
|
||||||
public Instance(@NotNull DynamicRegistry<DimensionType> dimensionTypeRegistry, @NotNull UUID uniqueId, @NotNull DynamicRegistry.Key<DimensionType> dimensionType, @NotNull NamespaceID dimensionName) {
|
public Instance(@NotNull DynamicRegistry<DimensionType> dimensionTypeRegistry, @NotNull UUID uuid, @NotNull DynamicRegistry.Key<DimensionType> dimensionType, @NotNull NamespaceID dimensionName) {
|
||||||
this.uniqueId = uniqueId;
|
this.uuid = uuid;
|
||||||
this.dimensionType = dimensionType;
|
this.dimensionType = dimensionType;
|
||||||
this.cachedDimensionType = dimensionTypeRegistry.get(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)`).");
|
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();
|
targetBorderDiameter = this.worldBorder.diameter();
|
||||||
|
|
||||||
this.pointers = Pointers.builder()
|
this.pointers = Pointers.builder()
|
||||||
.withDynamic(Identity.UUID, this::getUniqueId)
|
.withDynamic(Identity.UUID, this::getUuid)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final ServerProcess process = MinecraftServer.process();
|
final ServerProcess process = MinecraftServer.process();
|
||||||
@ -750,8 +750,19 @@ public abstract class Instance implements Block.Getter, Block.Setter,
|
|||||||
*
|
*
|
||||||
* @return the instance unique id
|
* @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() {
|
public @NotNull UUID getUniqueId() {
|
||||||
return uniqueId;
|
return uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,30 +89,30 @@ public class InstanceContainer extends Instance {
|
|||||||
protected InstanceContainer srcInstance; // only present if this instance has been created using a copy
|
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)
|
private long lastBlockChangeTime; // Time at which the last block change happened (#setBlock)
|
||||||
|
|
||||||
public InstanceContainer(@NotNull UUID uniqueId, @NotNull DynamicRegistry.Key<DimensionType> dimensionType) {
|
public InstanceContainer(@NotNull UUID uuid, @NotNull DynamicRegistry.Key<DimensionType> dimensionType) {
|
||||||
this(uniqueId, dimensionType, null, dimensionType.namespace());
|
this(uuid, dimensionType, null, dimensionType.namespace());
|
||||||
}
|
}
|
||||||
|
|
||||||
public InstanceContainer(@NotNull UUID uniqueId, @NotNull DynamicRegistry.Key<DimensionType> dimensionType, @NotNull NamespaceID dimensionName) {
|
public InstanceContainer(@NotNull UUID uuid, @NotNull DynamicRegistry.Key<DimensionType> dimensionType, @NotNull NamespaceID dimensionName) {
|
||||||
this(uniqueId, dimensionType, null, dimensionName);
|
this(uuid, dimensionType, null, dimensionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InstanceContainer(@NotNull UUID uniqueId, @NotNull DynamicRegistry.Key<DimensionType> dimensionType, @Nullable IChunkLoader loader) {
|
public InstanceContainer(@NotNull UUID uuid, @NotNull DynamicRegistry.Key<DimensionType> dimensionType, @Nullable IChunkLoader loader) {
|
||||||
this(uniqueId, dimensionType, loader, dimensionType.namespace());
|
this(uuid, dimensionType, loader, dimensionType.namespace());
|
||||||
}
|
}
|
||||||
|
|
||||||
public InstanceContainer(@NotNull UUID uniqueId, @NotNull DynamicRegistry.Key<DimensionType> dimensionType, @Nullable IChunkLoader loader, @NotNull NamespaceID dimensionName) {
|
public InstanceContainer(@NotNull UUID uuid, @NotNull DynamicRegistry.Key<DimensionType> dimensionType, @Nullable IChunkLoader loader, @NotNull NamespaceID dimensionName) {
|
||||||
this(MinecraftServer.getDimensionTypeRegistry(), uniqueId, dimensionType, loader, dimensionName);
|
this(MinecraftServer.getDimensionTypeRegistry(), uuid, dimensionType, loader, dimensionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InstanceContainer(
|
public InstanceContainer(
|
||||||
@NotNull DynamicRegistry<DimensionType> dimensionTypeRegistry,
|
@NotNull DynamicRegistry<DimensionType> dimensionTypeRegistry,
|
||||||
@NotNull UUID uniqueId,
|
@NotNull UUID uuid,
|
||||||
@NotNull DynamicRegistry.Key<DimensionType> dimensionType,
|
@NotNull DynamicRegistry.Key<DimensionType> dimensionType,
|
||||||
@Nullable IChunkLoader loader,
|
@Nullable IChunkLoader loader,
|
||||||
@NotNull NamespaceID dimensionName
|
@NotNull NamespaceID dimensionName
|
||||||
) {
|
) {
|
||||||
super(dimensionTypeRegistry, uniqueId, dimensionType, dimensionName);
|
super(dimensionTypeRegistry, uuid, dimensionType, dimensionName);
|
||||||
setChunkSupplier(DynamicChunk::new);
|
setChunkSupplier(DynamicChunk::new);
|
||||||
setChunkLoader(Objects.requireNonNullElse(loader, DEFAULT_LOADER));
|
setChunkLoader(Objects.requireNonNullElse(loader, DEFAULT_LOADER));
|
||||||
this.chunkLoader.loadInstance(this);
|
this.chunkLoader.loadInstance(this);
|
||||||
|
@ -151,7 +151,7 @@ public final class InstanceManager {
|
|||||||
public @Nullable Instance getInstance(@NotNull UUID uuid) {
|
public @Nullable Instance getInstance(@NotNull UUID uuid) {
|
||||||
Optional<Instance> instance = getInstances()
|
Optional<Instance> instance = getInstances()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(someInstance -> someInstance.getUniqueId().equals(uuid))
|
.filter(someInstance -> someInstance.getUuid().equals(uuid))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
return instance.orElse(null);
|
return instance.orElse(null);
|
||||||
}
|
}
|
||||||
|
@ -241,14 +241,13 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
@Override
|
@Override
|
||||||
protected LightData createLightData(boolean requiredFullChunk) {
|
protected LightData createLightData(boolean requiredFullChunk) {
|
||||||
packetGenerationLock.lock();
|
packetGenerationLock.lock();
|
||||||
|
try {
|
||||||
if (requiredFullChunk) {
|
if (requiredFullChunk) {
|
||||||
if (fullLightData != null) {
|
if (fullLightData != null) {
|
||||||
packetGenerationLock.unlock();
|
|
||||||
return fullLightData;
|
return fullLightData;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (partialLightData != null) {
|
if (partialLightData != null) {
|
||||||
packetGenerationLock.unlock();
|
|
||||||
return partialLightData;
|
return partialLightData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -329,9 +328,11 @@ public class LightingChunk extends DynamicChunk {
|
|||||||
this.partialLightData = lightData;
|
this.partialLightData = lightData;
|
||||||
}
|
}
|
||||||
|
|
||||||
packetGenerationLock.unlock();
|
|
||||||
|
|
||||||
return lightData;
|
return lightData;
|
||||||
|
} finally {
|
||||||
|
packetGenerationLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -21,8 +21,8 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
public class SharedInstance extends Instance {
|
public class SharedInstance extends Instance {
|
||||||
private final InstanceContainer instanceContainer;
|
private final InstanceContainer instanceContainer;
|
||||||
|
|
||||||
public SharedInstance(@NotNull UUID uniqueId, @NotNull InstanceContainer instanceContainer) {
|
public SharedInstance(@NotNull UUID uuid, @NotNull InstanceContainer instanceContainer) {
|
||||||
super(uniqueId, instanceContainer.getDimensionType());
|
super(uuid, instanceContainer.getDimensionType());
|
||||||
this.instanceContainer = instanceContainer;
|
this.instanceContainer = instanceContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ public class ChunkBatch implements Batch<ChunkCallback> {
|
|||||||
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
|
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
|
||||||
if (chunk == null) {
|
if (chunk == null) {
|
||||||
LOGGER.warn("Unable to apply ChunkBatch to unloaded chunk ({}, {}) in {}.",
|
LOGGER.warn("Unable to apply ChunkBatch to unloaded chunk ({}, {}) in {}.",
|
||||||
chunkX, chunkZ, instance.getUniqueId());
|
chunkX, chunkZ, instance.getUuid());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return apply(instance, chunk, callback);
|
return apply(instance, chunk, callback);
|
||||||
@ -165,7 +165,7 @@ public class ChunkBatch implements Batch<ChunkCallback> {
|
|||||||
try {
|
try {
|
||||||
if (!chunk.isLoaded()) {
|
if (!chunk.isLoaded()) {
|
||||||
LOGGER.warn("Unable to apply ChunkBatch to unloaded chunk ({}, {}) in {}.",
|
LOGGER.warn("Unable to apply ChunkBatch to unloaded chunk ({}, {}) in {}.",
|
||||||
chunk.getChunkX(), chunk.getChunkZ(), instance.getUniqueId());
|
chunk.getChunkX(), chunk.getChunkZ(), instance.getUuid());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public sealed interface BannerPattern extends ProtocolObject, BannerPatterns permits BannerPatternImpl {
|
public sealed interface BannerPattern extends ProtocolObject, BannerPatterns permits BannerPatternImpl {
|
||||||
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<BannerPattern>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::bannerPattern);
|
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<BannerPattern>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::bannerPattern, true);
|
||||||
@NotNull BinaryTagSerializer<DynamicRegistry.Key<BannerPattern>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::bannerPattern);
|
@NotNull BinaryTagSerializer<DynamicRegistry.Key<BannerPattern>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::bannerPattern);
|
||||||
|
|
||||||
static @NotNull BannerPattern create(
|
static @NotNull BannerPattern create(
|
||||||
|
@ -14,7 +14,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
public sealed interface JukeboxSong extends ProtocolObject, JukeboxSongs permits JukeboxSongImpl {
|
public sealed interface JukeboxSong extends ProtocolObject, JukeboxSongs permits JukeboxSongImpl {
|
||||||
|
|
||||||
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<JukeboxSong>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::jukeboxSong);
|
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<JukeboxSong>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::jukeboxSong, false);
|
||||||
@NotNull BinaryTagSerializer<DynamicRegistry.Key<JukeboxSong>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::jukeboxSong);
|
@NotNull BinaryTagSerializer<DynamicRegistry.Key<JukeboxSong>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::jukeboxSong);
|
||||||
|
|
||||||
static @NotNull JukeboxSong create(
|
static @NotNull JukeboxSong create(
|
||||||
|
@ -48,7 +48,7 @@ public interface UnitModifier extends Block.Setter, Biome.Setter {
|
|||||||
void fill(@NotNull Point start, @NotNull Point end, @NotNull Block block);
|
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 minHeight the minimum height of the area
|
||||||
* @param maxHeight the maximum height of the area
|
* @param maxHeight the maximum height of the area
|
||||||
|
@ -33,7 +33,7 @@ final class BlockLight implements Light {
|
|||||||
ShortArrayFIFOQueue lightSources = new ShortArrayFIFOQueue();
|
ShortArrayFIFOQueue lightSources = new ShortArrayFIFOQueue();
|
||||||
// Apply section light
|
// Apply section light
|
||||||
blockPalette.getAllPresent((x, y, z, stateId) -> {
|
blockPalette.getAllPresent((x, y, z, stateId) -> {
|
||||||
final Block block = Block.fromStateId((short) stateId);
|
final Block block = Block.fromStateId(stateId);
|
||||||
assert block != null;
|
assert block != null;
|
||||||
final byte lightEmission = (byte) block.registry().lightEmission();
|
final byte lightEmission = (byte) block.registry().lightEmission();
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ public final class LightCompute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Block getBlock(Palette palette, int x, int y, int z) {
|
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) {
|
public static byte[] bake(byte[] content1, byte[] content2) {
|
||||||
|
@ -115,6 +115,7 @@ public interface Palette {
|
|||||||
if (bitsPerEntry == 0) {
|
if (bitsPerEntry == 0) {
|
||||||
// Single valued 0-0
|
// Single valued 0-0
|
||||||
final int value = buffer.read(VAR_INT);
|
final int value = buffer.read(VAR_INT);
|
||||||
|
buffer.read(VAR_INT); // Skip size
|
||||||
return new PaletteSingle((byte) dimension, value);
|
return new PaletteSingle((byte) dimension, value);
|
||||||
} else if (bitsPerEntry >= minIndirect && bitsPerEntry <= maxIndirect) {
|
} else if (bitsPerEntry >= minIndirect && bitsPerEntry <= maxIndirect) {
|
||||||
// Indirect palette
|
// Indirect palette
|
||||||
|
@ -49,8 +49,6 @@ final class PaletteIndirect implements SpecializedPalette, Cloneable {
|
|||||||
this.paletteToValueList.add(palette[i]);
|
this.paletteToValueList.add(palette[i]);
|
||||||
this.valueToPaletteMap.put(palette[i], i);
|
this.valueToPaletteMap.put(palette[i], i);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.values = new long[arrayLength(dimension(), bitsPerEntry)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PaletteIndirect(int dimension, int maxBitsPerEntry, byte bitsPerEntry) {
|
PaletteIndirect(int dimension, int maxBitsPerEntry, byte bitsPerEntry) {
|
||||||
|
@ -77,7 +77,7 @@ public final class ItemComponent {
|
|||||||
public static final DataComponent<WritableBookContent> WRITABLE_BOOK_CONTENT = register("writable_book_content", WritableBookContent.NETWORK_TYPE, WritableBookContent.NBT_TYPE);
|
public static final DataComponent<WritableBookContent> WRITABLE_BOOK_CONTENT = register("writable_book_content", WritableBookContent.NETWORK_TYPE, WritableBookContent.NBT_TYPE);
|
||||||
public static final DataComponent<WrittenBookContent> WRITTEN_BOOK_CONTENT = register("written_book_content", WrittenBookContent.NETWORK_TYPE, WrittenBookContent.NBT_TYPE);
|
public static final DataComponent<WrittenBookContent> WRITTEN_BOOK_CONTENT = register("written_book_content", WrittenBookContent.NETWORK_TYPE, WrittenBookContent.NBT_TYPE);
|
||||||
public static final DataComponent<ArmorTrim> TRIM = register("trim", ArmorTrim.NETWORK_TYPE, ArmorTrim.NBT_TYPE);
|
public static final DataComponent<ArmorTrim> TRIM = register("trim", ArmorTrim.NETWORK_TYPE, ArmorTrim.NBT_TYPE);
|
||||||
public static final DataComponent<DebugStickState> DEBUG_STICK_STATE = register("debug_stick_state", null, DebugStickState.NBT_TYPE);
|
public static final DataComponent<DebugStickState> DEBUG_STICK_STATE = register("debug_stick_state", DebugStickState.NETWORK_TYPE, DebugStickState.NBT_TYPE);
|
||||||
public static final DataComponent<CustomData> ENTITY_DATA = register("entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE);
|
public static final DataComponent<CustomData> ENTITY_DATA = register("entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE);
|
||||||
public static final DataComponent<CustomData> BUCKET_ENTITY_DATA = register("bucket_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE);
|
public static final DataComponent<CustomData> BUCKET_ENTITY_DATA = register("bucket_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE);
|
||||||
public static final DataComponent<CustomData> BLOCK_ENTITY_DATA = register("block_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE);
|
public static final DataComponent<CustomData> BLOCK_ENTITY_DATA = register("block_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE);
|
||||||
|
@ -17,7 +17,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public sealed interface TrimMaterial extends ProtocolObject permits TrimMaterialImpl {
|
public sealed interface TrimMaterial extends ProtocolObject permits TrimMaterialImpl {
|
||||||
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<TrimMaterial>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::trimMaterial);
|
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<TrimMaterial>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::trimMaterial, true);
|
||||||
@NotNull BinaryTagSerializer<DynamicRegistry.Key<TrimMaterial>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::trimMaterial);
|
@NotNull BinaryTagSerializer<DynamicRegistry.Key<TrimMaterial>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::trimMaterial);
|
||||||
|
|
||||||
static @NotNull TrimMaterial create(
|
static @NotNull TrimMaterial create(
|
||||||
|
@ -15,7 +15,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public sealed interface TrimPattern extends ProtocolObject permits TrimPatternImpl {
|
public sealed interface TrimPattern extends ProtocolObject permits TrimPatternImpl {
|
||||||
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<TrimPattern>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::trimPattern);
|
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<TrimPattern>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::trimPattern, true);
|
||||||
@NotNull BinaryTagSerializer<DynamicRegistry.Key<TrimPattern>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::trimPattern);
|
@NotNull BinaryTagSerializer<DynamicRegistry.Key<TrimPattern>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::trimPattern);
|
||||||
|
|
||||||
static @NotNull TrimPattern create(
|
static @NotNull TrimPattern create(
|
||||||
|
@ -3,6 +3,7 @@ package net.minestom.server.item.component;
|
|||||||
import net.kyori.adventure.nbt.BinaryTag;
|
import net.kyori.adventure.nbt.BinaryTag;
|
||||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||||
import net.kyori.adventure.nbt.StringBinaryTag;
|
import net.kyori.adventure.nbt.StringBinaryTag;
|
||||||
|
import net.minestom.server.network.NetworkBuffer;
|
||||||
import net.minestom.server.utils.nbt.BinaryTagSerializer;
|
import net.minestom.server.utils.nbt.BinaryTagSerializer;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@ -30,6 +31,7 @@ public record DebugStickState(@NotNull Map<String, String> state) {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
public static final NetworkBuffer.Type<DebugStickState> NETWORK_TYPE = NetworkBuffer.TypedNBT(NBT_TYPE);
|
||||||
|
|
||||||
public DebugStickState {
|
public DebugStickState {
|
||||||
state = Map.copyOf(state);
|
state = Map.copyOf(state);
|
||||||
|
@ -16,7 +16,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public sealed interface Enchantment extends ProtocolObject, Enchantments permits EnchantmentImpl {
|
public sealed interface Enchantment extends ProtocolObject, Enchantments permits EnchantmentImpl {
|
||||||
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<Enchantment>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::enchantment);
|
@NotNull NetworkBuffer.Type<DynamicRegistry.Key<Enchantment>> NETWORK_TYPE = NetworkBuffer.RegistryKey(Registries::enchantment, false);
|
||||||
@NotNull BinaryTagSerializer<DynamicRegistry.Key<Enchantment>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::enchantment);
|
@NotNull BinaryTagSerializer<DynamicRegistry.Key<Enchantment>> NBT_TYPE = BinaryTagSerializer.registryKey(Registries::enchantment);
|
||||||
|
|
||||||
static @NotNull Builder builder() {
|
static @NotNull Builder builder() {
|
||||||
|
@ -35,7 +35,7 @@ public class SpectateListener {
|
|||||||
|
|
||||||
// Ignore if they're not in the same instance. Vanilla actually allows for
|
// 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.
|
// cross-instance spectating, but it's not really a good idea for Minestom.
|
||||||
if (targetInstance.getUniqueId() != playerInstance.getUniqueId()) {
|
if (targetInstance.getUuid() != playerInstance.getUuid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package net.minestom.server.listener;
|
package net.minestom.server.listener;
|
||||||
|
|
||||||
import net.minestom.server.ServerFlag;
|
import net.minestom.server.ServerFlag;
|
||||||
|
import net.minestom.server.collision.BoundingBox;
|
||||||
import net.minestom.server.coordinate.Point;
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import net.minestom.server.coordinate.Pos;
|
||||||
import net.minestom.server.coordinate.Vec;
|
import net.minestom.server.coordinate.Vec;
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.LivingEntity;
|
import net.minestom.server.entity.LivingEntity;
|
||||||
@ -20,8 +22,11 @@ public class UseEntityListener {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (ServerFlag.ENFORCE_INTERACTION_LIMIT) {
|
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
|
final double maxDistanceSquared = Math.pow(player.getAttributeValue(Attribute.ENTITY_INTERACTION_RANGE) + 1, 2);
|
||||||
if (player.getDistanceSquared(entity) > range) {
|
|
||||||
|
final double distSquared = getDistSquared(player, entity);
|
||||||
|
|
||||||
|
if (distSquared > maxDistanceSquared) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,4 +41,33 @@ public class UseEntityListener {
|
|||||||
EventDispatcher.call(new PlayerEntityInteractEvent(player, entity, interactAt.hand(), interactPosition));
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
@ -45,33 +45,33 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
|
|
||||||
private void writeInnerComponent(@NotNull NetworkBuffer buffer, @NotNull Component component) {
|
private void writeInnerComponent(@NotNull NetworkBuffer buffer, @NotNull Component component) {
|
||||||
buffer.write(BYTE, TAG_STRING); // Start first tag (always the type)
|
buffer.write(BYTE, TAG_STRING); // Start first tag (always the type)
|
||||||
writeUtf(buffer, "type");
|
buffer.write(STRING_IO_UTF8, "type");
|
||||||
switch (component) {
|
switch (component) {
|
||||||
case TextComponent text -> {
|
case TextComponent text -> {
|
||||||
writeUtf(buffer, "text");
|
buffer.write(STRING_IO_UTF8, "text");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING); // Start "text" tag
|
buffer.write(BYTE, TAG_STRING); // Start "text" tag
|
||||||
writeUtf(buffer, "text");
|
buffer.write(STRING_IO_UTF8, "text");
|
||||||
writeUtf(buffer, text.content());
|
buffer.write(STRING_IO_UTF8, text.content());
|
||||||
}
|
}
|
||||||
case TranslatableComponent translatable -> {
|
case TranslatableComponent translatable -> {
|
||||||
writeUtf(buffer, "translatable");
|
buffer.write(STRING_IO_UTF8, "translatable");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING); // Start "translate" tag
|
buffer.write(BYTE, TAG_STRING); // Start "translate" tag
|
||||||
writeUtf(buffer, "translate");
|
buffer.write(STRING_IO_UTF8, "translate");
|
||||||
writeUtf(buffer, translatable.key());
|
buffer.write(STRING_IO_UTF8, translatable.key());
|
||||||
|
|
||||||
final String fallback = translatable.fallback();
|
final String fallback = translatable.fallback();
|
||||||
if (fallback != null) {
|
if (fallback != null) {
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "fallback");
|
buffer.write(STRING_IO_UTF8, "fallback");
|
||||||
writeUtf(buffer, fallback);
|
buffer.write(STRING_IO_UTF8, fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<TranslationArgument> args = translatable.arguments();
|
final List<TranslationArgument> args = translatable.arguments();
|
||||||
if (!args.isEmpty()) {
|
if (!args.isEmpty()) {
|
||||||
buffer.write(BYTE, TAG_LIST);
|
buffer.write(BYTE, TAG_LIST);
|
||||||
writeUtf(buffer, "with");
|
buffer.write(STRING_IO_UTF8, "with");
|
||||||
buffer.write(BYTE, TAG_COMPOUND); // List type
|
buffer.write(BYTE, TAG_COMPOUND); // List type
|
||||||
buffer.write(INT, args.size());
|
buffer.write(INT, args.size());
|
||||||
for (final TranslationArgument arg : args)
|
for (final TranslationArgument arg : args)
|
||||||
@ -79,42 +79,42 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ScoreComponent score -> {
|
case ScoreComponent score -> {
|
||||||
writeUtf(buffer, "score");
|
buffer.write(STRING_IO_UTF8, "score");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_COMPOUND); // Start "score" tag
|
buffer.write(BYTE, TAG_COMPOUND); // Start "score" tag
|
||||||
writeUtf(buffer, "score");
|
buffer.write(STRING_IO_UTF8, "score");
|
||||||
{
|
{
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "name");
|
buffer.write(STRING_IO_UTF8, "name");
|
||||||
writeUtf(buffer, score.name());
|
buffer.write(STRING_IO_UTF8, score.name());
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "objective");
|
buffer.write(STRING_IO_UTF8, "objective");
|
||||||
writeUtf(buffer, score.objective());
|
buffer.write(STRING_IO_UTF8, score.objective());
|
||||||
}
|
}
|
||||||
buffer.write(BYTE, TAG_END); // End "score" tag
|
buffer.write(BYTE, TAG_END); // End "score" tag
|
||||||
|
|
||||||
}
|
}
|
||||||
case SelectorComponent selector -> {
|
case SelectorComponent selector -> {
|
||||||
writeUtf(buffer, "selector");
|
buffer.write(STRING_IO_UTF8, "selector");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "selector");
|
buffer.write(STRING_IO_UTF8, "selector");
|
||||||
writeUtf(buffer, selector.pattern());
|
buffer.write(STRING_IO_UTF8, selector.pattern());
|
||||||
|
|
||||||
final Component separator = selector.separator();
|
final Component separator = selector.separator();
|
||||||
if (separator != null) {
|
if (separator != null) {
|
||||||
buffer.write(BYTE, TAG_COMPOUND);
|
buffer.write(BYTE, TAG_COMPOUND);
|
||||||
writeUtf(buffer, "separator");
|
buffer.write(STRING_IO_UTF8, "separator");
|
||||||
writeInnerComponent(buffer, separator);
|
writeInnerComponent(buffer, separator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case KeybindComponent keybind -> {
|
case KeybindComponent keybind -> {
|
||||||
writeUtf(buffer, "keybind");
|
buffer.write(STRING_IO_UTF8, "keybind");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "keybind");
|
buffer.write(STRING_IO_UTF8, "keybind");
|
||||||
writeUtf(buffer, keybind.keybind());
|
buffer.write(STRING_IO_UTF8, keybind.keybind());
|
||||||
}
|
}
|
||||||
case NBTComponent<?, ?> nbt -> {
|
case NBTComponent<?, ?> nbt -> {
|
||||||
//todo
|
//todo
|
||||||
@ -126,7 +126,7 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
// Children
|
// Children
|
||||||
if (!component.children().isEmpty()) {
|
if (!component.children().isEmpty()) {
|
||||||
buffer.write(BYTE, TAG_LIST);
|
buffer.write(BYTE, TAG_LIST);
|
||||||
writeUtf(buffer, "extra");
|
buffer.write(STRING_IO_UTF8, "extra");
|
||||||
buffer.write(BYTE, TAG_COMPOUND); // List type
|
buffer.write(BYTE, TAG_COMPOUND); // List type
|
||||||
|
|
||||||
buffer.write(INT, component.children().size());
|
buffer.write(INT, component.children().size());
|
||||||
@ -144,59 +144,59 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
final TextColor color = style.color();
|
final TextColor color = style.color();
|
||||||
if (color != null) {
|
if (color != null) {
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "color");
|
buffer.write(STRING_IO_UTF8, "color");
|
||||||
if (color instanceof NamedTextColor namedColor)
|
if (color instanceof NamedTextColor namedColor)
|
||||||
writeUtf(buffer, namedColor.toString());
|
buffer.write(STRING_IO_UTF8, namedColor.toString());
|
||||||
else writeUtf(buffer, color.asHexString());
|
else buffer.write(STRING_IO_UTF8, color.asHexString());
|
||||||
}
|
}
|
||||||
|
|
||||||
final Key font = style.font();
|
final Key font = style.font();
|
||||||
if (font != null) {
|
if (font != null) {
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "font");
|
buffer.write(STRING_IO_UTF8, "font");
|
||||||
writeUtf(buffer, font.asString());
|
buffer.write(STRING_IO_UTF8, font.asString());
|
||||||
}
|
}
|
||||||
|
|
||||||
final TextDecoration.State bold = style.decoration(TextDecoration.BOLD);
|
final TextDecoration.State bold = style.decoration(TextDecoration.BOLD);
|
||||||
if (bold != TextDecoration.State.NOT_SET) {
|
if (bold != TextDecoration.State.NOT_SET) {
|
||||||
buffer.write(BYTE, TAG_BYTE);
|
buffer.write(BYTE, TAG_BYTE);
|
||||||
writeUtf(buffer, "bold");
|
buffer.write(STRING_IO_UTF8, "bold");
|
||||||
buffer.write(BYTE, bold == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
buffer.write(BYTE, bold == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TextDecoration.State italic = style.decoration(TextDecoration.ITALIC);
|
final TextDecoration.State italic = style.decoration(TextDecoration.ITALIC);
|
||||||
if (italic != TextDecoration.State.NOT_SET) {
|
if (italic != TextDecoration.State.NOT_SET) {
|
||||||
buffer.write(BYTE, TAG_BYTE);
|
buffer.write(BYTE, TAG_BYTE);
|
||||||
writeUtf(buffer, "italic");
|
buffer.write(STRING_IO_UTF8, "italic");
|
||||||
buffer.write(BYTE, italic == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
buffer.write(BYTE, italic == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TextDecoration.State underlined = style.decoration(TextDecoration.UNDERLINED);
|
final TextDecoration.State underlined = style.decoration(TextDecoration.UNDERLINED);
|
||||||
if (underlined != TextDecoration.State.NOT_SET) {
|
if (underlined != TextDecoration.State.NOT_SET) {
|
||||||
buffer.write(BYTE, TAG_BYTE);
|
buffer.write(BYTE, TAG_BYTE);
|
||||||
writeUtf(buffer, "underlined");
|
buffer.write(STRING_IO_UTF8, "underlined");
|
||||||
buffer.write(BYTE, underlined == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
buffer.write(BYTE, underlined == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TextDecoration.State strikethrough = style.decoration(TextDecoration.STRIKETHROUGH);
|
final TextDecoration.State strikethrough = style.decoration(TextDecoration.STRIKETHROUGH);
|
||||||
if (strikethrough != TextDecoration.State.NOT_SET) {
|
if (strikethrough != TextDecoration.State.NOT_SET) {
|
||||||
buffer.write(BYTE, TAG_BYTE);
|
buffer.write(BYTE, TAG_BYTE);
|
||||||
writeUtf(buffer, "strikethrough");
|
buffer.write(STRING_IO_UTF8, "strikethrough");
|
||||||
buffer.write(BYTE, strikethrough == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
buffer.write(BYTE, strikethrough == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TextDecoration.State obfuscated = style.decoration(TextDecoration.OBFUSCATED);
|
final TextDecoration.State obfuscated = style.decoration(TextDecoration.OBFUSCATED);
|
||||||
if (obfuscated != TextDecoration.State.NOT_SET) {
|
if (obfuscated != TextDecoration.State.NOT_SET) {
|
||||||
buffer.write(BYTE, TAG_BYTE);
|
buffer.write(BYTE, TAG_BYTE);
|
||||||
writeUtf(buffer, "obfuscated");
|
buffer.write(STRING_IO_UTF8, "obfuscated");
|
||||||
buffer.write(BYTE, obfuscated == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
buffer.write(BYTE, obfuscated == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String insertion = style.insertion();
|
final String insertion = style.insertion();
|
||||||
if (insertion != null) {
|
if (insertion != null) {
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "insertion");
|
buffer.write(STRING_IO_UTF8, "insertion");
|
||||||
writeUtf(buffer, insertion);
|
buffer.write(STRING_IO_UTF8, insertion);
|
||||||
}
|
}
|
||||||
|
|
||||||
final ClickEvent clickEvent = style.clickEvent();
|
final ClickEvent clickEvent = style.clickEvent();
|
||||||
@ -208,15 +208,15 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
|
|
||||||
private void writeClickEvent(@NotNull NetworkBuffer buffer, @NotNull ClickEvent clickEvent) {
|
private void writeClickEvent(@NotNull NetworkBuffer buffer, @NotNull ClickEvent clickEvent) {
|
||||||
buffer.write(BYTE, TAG_COMPOUND);
|
buffer.write(BYTE, TAG_COMPOUND);
|
||||||
writeUtf(buffer, "clickEvent");
|
buffer.write(STRING_IO_UTF8, "clickEvent");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "action");
|
buffer.write(STRING_IO_UTF8, "action");
|
||||||
writeUtf(buffer, clickEvent.action().name().toLowerCase(Locale.ROOT));
|
buffer.write(STRING_IO_UTF8, clickEvent.action().name().toLowerCase(Locale.ROOT));
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "value");
|
buffer.write(STRING_IO_UTF8, "value");
|
||||||
writeUtf(buffer, clickEvent.value());
|
buffer.write(STRING_IO_UTF8, clickEvent.value());
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_END);
|
buffer.write(BYTE, TAG_END);
|
||||||
}
|
}
|
||||||
@ -224,29 +224,29 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void writeHoverEvent(@NotNull NetworkBuffer buffer, @NotNull HoverEvent<?> hoverEvent) {
|
private void writeHoverEvent(@NotNull NetworkBuffer buffer, @NotNull HoverEvent<?> hoverEvent) {
|
||||||
buffer.write(BYTE, TAG_COMPOUND);
|
buffer.write(BYTE, TAG_COMPOUND);
|
||||||
writeUtf(buffer, "hoverEvent");
|
buffer.write(STRING_IO_UTF8, "hoverEvent");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "action");
|
buffer.write(STRING_IO_UTF8, "action");
|
||||||
writeUtf(buffer, hoverEvent.action().toString().toLowerCase(Locale.ROOT));
|
buffer.write(STRING_IO_UTF8, hoverEvent.action().toString().toLowerCase(Locale.ROOT));
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_COMPOUND); // Start contents tag
|
buffer.write(BYTE, TAG_COMPOUND); // Start contents tag
|
||||||
writeUtf(buffer, "contents");
|
buffer.write(STRING_IO_UTF8, "contents");
|
||||||
if (hoverEvent.action() == HoverEvent.Action.SHOW_TEXT) {
|
if (hoverEvent.action() == HoverEvent.Action.SHOW_TEXT) {
|
||||||
writeInnerComponent(buffer, (Component) hoverEvent.value());
|
writeInnerComponent(buffer, (Component) hoverEvent.value());
|
||||||
} else if (hoverEvent.action() == HoverEvent.Action.SHOW_ITEM) {
|
} else if (hoverEvent.action() == HoverEvent.Action.SHOW_ITEM) {
|
||||||
var value = ((HoverEvent<HoverEvent.ShowItem>) hoverEvent).value();
|
var value = ((HoverEvent<HoverEvent.ShowItem>) hoverEvent).value();
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "id");
|
buffer.write(STRING_IO_UTF8, "id");
|
||||||
writeUtf(buffer, value.item().asString());
|
buffer.write(STRING_IO_UTF8, value.item().asString());
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_INT);
|
buffer.write(BYTE, TAG_INT);
|
||||||
writeUtf(buffer, "count");
|
buffer.write(STRING_IO_UTF8, "count");
|
||||||
buffer.write(INT, value.count());
|
buffer.write(INT, value.count());
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_COMPOUND);
|
buffer.write(BYTE, TAG_COMPOUND);
|
||||||
writeUtf(buffer, "components");
|
buffer.write(STRING_IO_UTF8, "components");
|
||||||
//todo item components
|
//todo item components
|
||||||
buffer.write(BYTE, TAG_END);
|
buffer.write(BYTE, TAG_END);
|
||||||
|
|
||||||
@ -257,17 +257,17 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
final Component name = value.name();
|
final Component name = value.name();
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
buffer.write(BYTE, TAG_COMPOUND);
|
buffer.write(BYTE, TAG_COMPOUND);
|
||||||
writeUtf(buffer, "name");
|
buffer.write(STRING_IO_UTF8, "name");
|
||||||
writeInnerComponent(buffer, name);
|
writeInnerComponent(buffer, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "type");
|
buffer.write(STRING_IO_UTF8, "type");
|
||||||
writeUtf(buffer, value.type().asString());
|
buffer.write(STRING_IO_UTF8, value.type().asString());
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "id");
|
buffer.write(STRING_IO_UTF8, "id");
|
||||||
writeUtf(buffer, value.id().toString());
|
buffer.write(STRING_IO_UTF8, value.id().toString());
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_END); // End contents tag
|
buffer.write(BYTE, TAG_END); // End contents tag
|
||||||
} else {
|
} else {
|
||||||
@ -276,53 +276,4 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
|
|
||||||
buffer.write(BYTE, TAG_END);
|
buffer.write(BYTE, TAG_END);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a very gross version of {@link java.io.DataOutputStream#writeUTF(String)}. We need the data in the java
|
|
||||||
* modified utf-8 format, and I couldnt find a method without creating a new buffer for it.
|
|
||||||
*
|
|
||||||
* @param buffer the buffer to write to
|
|
||||||
* @param str the string to write
|
|
||||||
*/
|
|
||||||
private static void writeUtf(@NotNull NetworkBuffer buffer, @NotNull String str) {
|
|
||||||
final int strlen = str.length();
|
|
||||||
int utflen = strlen; // optimized for ASCII
|
|
||||||
|
|
||||||
for (int i = 0; i < strlen; i++) {
|
|
||||||
int c = str.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 = 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -333,6 +333,7 @@ public final class ConnectionManager {
|
|||||||
public void updateWaitingPlayers() {
|
public void updateWaitingPlayers() {
|
||||||
this.waitingPlayers.drain(player -> {
|
this.waitingPlayers.drain(player -> {
|
||||||
if (!player.isOnline()) return; // Player disconnected while in queued to join
|
if (!player.isOnline()) return; // Player disconnected while in queued to join
|
||||||
|
configurationPlayers.remove(player);
|
||||||
playPlayers.add(player);
|
playPlayers.add(player);
|
||||||
keepAlivePlayers.add(player);
|
keepAlivePlayers.add(player);
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import net.minestom.server.registry.Registries;
|
|||||||
import net.minestom.server.utils.Direction;
|
import net.minestom.server.utils.Direction;
|
||||||
import net.minestom.server.utils.Unit;
|
import net.minestom.server.utils.Unit;
|
||||||
import net.minestom.server.utils.crypto.KeyUtils;
|
import net.minestom.server.utils.crypto.KeyUtils;
|
||||||
|
import net.minestom.server.utils.nbt.BinaryTagSerializer;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.annotations.UnknownNullability;
|
import org.jetbrains.annotations.UnknownNullability;
|
||||||
@ -45,6 +46,7 @@ public sealed interface NetworkBuffer permits NetworkBufferImpl {
|
|||||||
Type<byte[]> RAW_BYTES = new NetworkBufferTypeImpl.RawBytesType(-1);
|
Type<byte[]> RAW_BYTES = new NetworkBufferTypeImpl.RawBytesType(-1);
|
||||||
Type<String> STRING = new NetworkBufferTypeImpl.StringType();
|
Type<String> STRING = new NetworkBufferTypeImpl.StringType();
|
||||||
Type<String> STRING_TERMINATED = new NetworkBufferTypeImpl.StringTerminatedType();
|
Type<String> STRING_TERMINATED = new NetworkBufferTypeImpl.StringTerminatedType();
|
||||||
|
Type<String> STRING_IO_UTF8 = new NetworkBufferTypeImpl.IOUTF8StringType();
|
||||||
Type<BinaryTag> NBT = new NetworkBufferTypeImpl.NbtType();
|
Type<BinaryTag> NBT = new NetworkBufferTypeImpl.NbtType();
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
Type<CompoundBinaryTag> NBT_COMPOUND = (Type) new NetworkBufferTypeImpl.NbtType();
|
Type<CompoundBinaryTag> NBT_COMPOUND = (Type) new NetworkBufferTypeImpl.NbtType();
|
||||||
@ -63,8 +65,8 @@ public sealed interface NetworkBuffer permits NetworkBufferImpl {
|
|||||||
Type<Instant> INSTANT_MS = LONG.transform(Instant::ofEpochMilli, Instant::toEpochMilli);
|
Type<Instant> INSTANT_MS = LONG.transform(Instant::ofEpochMilli, Instant::toEpochMilli);
|
||||||
Type<PublicKey> PUBLIC_KEY = BYTE_ARRAY.transform(KeyUtils::publicRSAKeyFrom, PublicKey::getEncoded);
|
Type<PublicKey> PUBLIC_KEY = BYTE_ARRAY.transform(KeyUtils::publicRSAKeyFrom, PublicKey::getEncoded);
|
||||||
|
|
||||||
static <T extends ProtocolObject> @NotNull Type<DynamicRegistry.Key<T>> RegistryKey(@NotNull Function<Registries, DynamicRegistry<T>> selector) {
|
static <T extends ProtocolObject> @NotNull Type<DynamicRegistry.Key<T>> RegistryKey(@NotNull Function<Registries, DynamicRegistry<T>> selector, boolean holder) {
|
||||||
return new NetworkBufferTypeImpl.RegistryTypeType<>(selector);
|
return new NetworkBufferTypeImpl.RegistryTypeType<>(selector, holder);
|
||||||
}
|
}
|
||||||
|
|
||||||
// METADATA
|
// METADATA
|
||||||
@ -104,6 +106,10 @@ public sealed interface NetworkBuffer permits NetworkBufferImpl {
|
|||||||
return new NetworkBufferTypeImpl.LazyType<>(supplier);
|
return new NetworkBufferTypeImpl.LazyType<>(supplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static <T> @NotNull Type<T> TypedNBT(@NotNull BinaryTagSerializer<T> serializer) {
|
||||||
|
return new NetworkBufferTypeImpl.TypedNbtType<>(serializer);
|
||||||
|
}
|
||||||
|
|
||||||
<T> void write(@NotNull Type<T> type, @UnknownNullability T value) throws IndexOutOfBoundsException;
|
<T> void write(@NotNull Type<T> type, @UnknownNullability T value) throws IndexOutOfBoundsException;
|
||||||
|
|
||||||
<T> @UnknownNullability T read(@NotNull Type<T> type) throws IndexOutOfBoundsException;
|
<T> @UnknownNullability T read(@NotNull Type<T> type) throws IndexOutOfBoundsException;
|
||||||
|
@ -13,6 +13,7 @@ import net.minestom.server.registry.ProtocolObject;
|
|||||||
import net.minestom.server.registry.Registries;
|
import net.minestom.server.registry.Registries;
|
||||||
import net.minestom.server.utils.Unit;
|
import net.minestom.server.utils.Unit;
|
||||||
import net.minestom.server.utils.nbt.BinaryTagReader;
|
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.nbt.BinaryTagWriter;
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -708,6 +709,23 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record TypedNbtType<T>(@NotNull BinaryTagSerializer<T> nbtType) implements NetworkBufferTypeImpl<T> {
|
||||||
|
@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<T, S>(@NotNull Type<T> parent, @NotNull Function<T, S> to,
|
record TransformType<T, S>(@NotNull Type<T> parent, @NotNull Function<T, S> to,
|
||||||
@NotNull Function<S, T> from) implements NetworkBufferTypeImpl<S> {
|
@NotNull Function<S, T> from) implements NetworkBufferTypeImpl<S> {
|
||||||
@Override
|
@Override
|
||||||
@ -794,15 +812,18 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
record RegistryTypeType<T extends ProtocolObject>(
|
record RegistryTypeType<T extends ProtocolObject>(
|
||||||
@NotNull Function<Registries, DynamicRegistry<T>> selector) implements NetworkBufferTypeImpl<DynamicRegistry.Key<T>> {
|
@NotNull Function<Registries, DynamicRegistry<T>> selector,
|
||||||
|
boolean holder
|
||||||
|
) implements NetworkBufferTypeImpl<DynamicRegistry.Key<T>> {
|
||||||
@Override
|
@Override
|
||||||
public void write(@NotNull NetworkBuffer buffer, DynamicRegistry.Key<T> value) {
|
public void write(@NotNull NetworkBuffer buffer, DynamicRegistry.Key<T> value) {
|
||||||
final Registries registries = impl(buffer).registries;
|
final Registries registries = impl(buffer).registries;
|
||||||
Check.stateCondition(registries == null, "Buffer does not have registries");
|
Check.stateCondition(registries == null, "Buffer does not have registries");
|
||||||
final DynamicRegistry<T> registry = selector.apply(registries);
|
final DynamicRegistry<T> 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.
|
// 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);
|
Check.argCondition(id == -1, "Key is not registered: {0} > {1}", registry, value);
|
||||||
buffer.write(VAR_INT, id);
|
buffer.write(VAR_INT, id);
|
||||||
}
|
}
|
||||||
@ -812,13 +833,131 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
|
|||||||
final Registries registries = impl(buffer).registries;
|
final Registries registries = impl(buffer).registries;
|
||||||
Check.stateCondition(registries == null, "Buffer does not have registries");
|
Check.stateCondition(registries == null, "Buffer does not have registries");
|
||||||
DynamicRegistry<T> registry = selector.apply(registries);
|
DynamicRegistry<T> 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<T> key = registry.getKey(id);
|
final DynamicRegistry.Key<T> key = registry.getKey(id);
|
||||||
Check.argCondition(key == null, "No such ID in registry: {0} > {1}", registry, id);
|
Check.argCondition(key == null, "No such ID in registry: {0} > {1}", registry, id);
|
||||||
return key;
|
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<String> {
|
||||||
|
@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 <T> long sizeOf(Type<T> type, T value, Registries registries) {
|
static <T> long sizeOf(Type<T> type, T value, Registries registries) {
|
||||||
NetworkBuffer buffer = NetworkBufferImpl.dummy(registries);
|
NetworkBuffer buffer = NetworkBufferImpl.dummy(registries);
|
||||||
type.write(buffer, value);
|
type.write(buffer, value);
|
||||||
|
@ -202,6 +202,7 @@ public final class PacketWriting {
|
|||||||
if (written < minWrite) {
|
if (written < minWrite) {
|
||||||
// Try again with a bigger buffer
|
// Try again with a bigger buffer
|
||||||
final long newSize = Math.min(buffer.capacity() * 2, ServerFlag.MAX_PACKET_SIZE);
|
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);
|
buffer.resize(newSize);
|
||||||
} else {
|
} else {
|
||||||
// At least one packet has been written
|
// At least one packet has been written
|
||||||
|
@ -10,16 +10,15 @@ import net.minestom.server.network.player.GameProfile;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.function.UnaryOperator;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static net.minestom.server.network.NetworkBuffer.*;
|
import static net.minestom.server.network.NetworkBuffer.*;
|
||||||
|
|
||||||
public record PlayerInfoUpdatePacket(
|
public record PlayerInfoUpdatePacket(
|
||||||
@NotNull EnumSet<@NotNull Action> actions,
|
@NotNull EnumSet<@NotNull Action> actions,
|
||||||
@NotNull List<@NotNull Entry> entries
|
@NotNull List<@NotNull Entry> entries
|
||||||
) implements ServerPacket.Play {
|
) implements ServerPacket.Play, ServerPacket.ComponentHolding {
|
||||||
public static final int MAX_ENTRIES = 1024;
|
public static final int MAX_ENTRIES = 1024;
|
||||||
|
|
||||||
public PlayerInfoUpdatePacket(@NotNull Action action, @NotNull Entry entry) {
|
public PlayerInfoUpdatePacket(@NotNull Action action, @NotNull Entry entry) {
|
||||||
@ -46,6 +45,33 @@ public record PlayerInfoUpdatePacket(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Collection<Component> components() {
|
||||||
|
final List<Component> 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<Component> operator) {
|
||||||
|
final List<Entry> 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<Property> properties,
|
public record Entry(UUID uuid, String username, List<Property> properties,
|
||||||
boolean listed, int latency, GameMode gameMode,
|
boolean listed, int latency, GameMode gameMode,
|
||||||
@Nullable Component displayName, @Nullable ChatSession chatSession,
|
@Nullable Component displayName, @Nullable ChatSession chatSession,
|
||||||
|
@ -141,9 +141,9 @@ public abstract class PlayerConnection {
|
|||||||
*/
|
*/
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
this.online = false;
|
this.online = false;
|
||||||
MinecraftServer.getConnectionManager().removePlayer(this);
|
final Player player = MinecraftServer.getConnectionManager().getPlayer(this);
|
||||||
final Player player = getPlayer();
|
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
|
MinecraftServer.getConnectionManager().removePlayer(this);
|
||||||
if (connectionState == ConnectionState.PLAY && !player.isRemoved())
|
if (connectionState == ConnectionState.PLAY && !player.isRemoved())
|
||||||
player.scheduleNextTick(Entity::remove);
|
player.scheduleNextTick(Entity::remove);
|
||||||
else {
|
else {
|
||||||
|
@ -137,6 +137,10 @@ public final class Registry {
|
|||||||
return new JukeboxSongEntry(namespace, main, null);
|
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 {
|
public static @NotNull InputStream loadRegistryFile(@NotNull Resource resource) throws IOException {
|
||||||
// 1. Try to load from jar resources
|
// 1. Try to load from jar resources
|
||||||
InputStream resourceStream = Registry.class.getClassLoader().getResourceAsStream(resource.name);
|
InputStream resourceStream = Registry.class.getClassLoader().getResourceAsStream(resource.name);
|
||||||
@ -245,6 +249,7 @@ public final class Registry {
|
|||||||
ENTITY_TYPE_TAGS("tags/entity_type.json"),
|
ENTITY_TYPE_TAGS("tags/entity_type.json"),
|
||||||
FLUID_TAGS("tags/fluid.json"),
|
FLUID_TAGS("tags/fluid.json"),
|
||||||
GAMEPLAY_TAGS("tags/game_event.json"),
|
GAMEPLAY_TAGS("tags/game_event.json"),
|
||||||
|
GAME_EVENTS("game_events.json"),
|
||||||
ITEM_TAGS("tags/item.json"),
|
ITEM_TAGS("tags/item.json"),
|
||||||
ENCHANTMENT_TAGS("tags/enchantment.json"),
|
ENCHANTMENT_TAGS("tags/enchantment.json"),
|
||||||
BIOME_TAGS("tags/biome.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 {
|
public static final class BlockEntry implements Entry {
|
||||||
private final NamespaceID namespace;
|
private final NamespaceID namespace;
|
||||||
private final int id;
|
private final int id;
|
||||||
|
@ -5,7 +5,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
|||||||
import net.minestom.server.entity.LivingEntity;
|
import net.minestom.server.entity.LivingEntity;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.utils.PacketSendingUtils;
|
import net.minestom.server.utils.PacketSendingUtils;
|
||||||
import net.minestom.server.utils.UniqueIdUtils;
|
import net.minestom.server.utils.UUIDUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -158,7 +158,7 @@ public final class TeamManager {
|
|||||||
public List<String> getPlayers(Team team) {
|
public List<String> getPlayers(Team team) {
|
||||||
List<String> players = new ArrayList<>();
|
List<String> players = new ArrayList<>();
|
||||||
for (String member : team.getMembers()) {
|
for (String member : team.getMembers()) {
|
||||||
boolean match = UniqueIdUtils.isUniqueId(member);
|
boolean match = UUIDUtils.isUuid(member);
|
||||||
|
|
||||||
if (!match) players.add(member);
|
if (!match) players.add(member);
|
||||||
}
|
}
|
||||||
@ -176,7 +176,7 @@ public final class TeamManager {
|
|||||||
public List<String> getEntities(Team team) {
|
public List<String> getEntities(Team team) {
|
||||||
List<String> entities = new ArrayList<>();
|
List<String> entities = new ArrayList<>();
|
||||||
for (String member : team.getMembers()) {
|
for (String member : team.getMembers()) {
|
||||||
boolean match = UniqueIdUtils.isUniqueId(member);
|
boolean match = UUIDUtils.isUuid(member);
|
||||||
|
|
||||||
if (match) entities.add(member);
|
if (match) entities.add(member);
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ public final class SnapshotImpl {
|
|||||||
final Section section = sections[globalToChunk(y) - minSection];
|
final Section section = sections[globalToChunk(y) - minSection];
|
||||||
final int blockStateId = section.blockPalette()
|
final int blockStateId = section.blockPalette()
|
||||||
.get(globalToSectionRelative(x), globalToSectionRelative(y), globalToSectionRelative(z));
|
.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
|
@Override
|
||||||
|
@ -5,7 +5,7 @@ import net.kyori.adventure.text.Component;
|
|||||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
import net.minestom.server.ServerFlag;
|
import net.minestom.server.ServerFlag;
|
||||||
import net.minestom.server.item.ItemStack;
|
import net.minestom.server.item.ItemStack;
|
||||||
import net.minestom.server.utils.UniqueIdUtils;
|
import net.minestom.server.utils.UUIDUtils;
|
||||||
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ final class Serializers {
|
|||||||
static final Entry<String, StringBinaryTag> STRING = new Entry<>(BinaryTagTypes.STRING, StringBinaryTag::value, StringBinaryTag::stringBinaryTag);
|
static final Entry<String, StringBinaryTag> STRING = new Entry<>(BinaryTagTypes.STRING, StringBinaryTag::value, StringBinaryTag::stringBinaryTag);
|
||||||
static final Entry<BinaryTag, BinaryTag> NBT_ENTRY = new Entry<>(null, Function.identity(), Function.identity());
|
static final Entry<BinaryTag, BinaryTag> NBT_ENTRY = new Entry<>(null, Function.identity(), Function.identity());
|
||||||
|
|
||||||
static final Entry<java.util.UUID, IntArrayBinaryTag> UUID = new Entry<>(BinaryTagTypes.INT_ARRAY, UniqueIdUtils::fromNbt, UniqueIdUtils::toNbt);
|
static final Entry<java.util.UUID, IntArrayBinaryTag> UUID = new Entry<>(BinaryTagTypes.INT_ARRAY, UUIDUtils::fromNbt, UUIDUtils::toNbt);
|
||||||
static final Entry<ItemStack, CompoundBinaryTag> ITEM = new Entry<>(BinaryTagTypes.COMPOUND, ItemStack::fromItemNBT, ItemStack::toItemNBT);
|
static final Entry<ItemStack, CompoundBinaryTag> ITEM = new Entry<>(BinaryTagTypes.COMPOUND, ItemStack::fromItemNBT, ItemStack::toItemNBT);
|
||||||
static final Entry<Component, StringBinaryTag> COMPONENT = new Entry<>(BinaryTagTypes.STRING, input -> GsonComponentSerializer.gson().deserialize(input.value()),
|
static final Entry<Component, StringBinaryTag> COMPONENT = new Entry<>(BinaryTagTypes.STRING, input -> GsonComponentSerializer.gson().deserialize(input.value()),
|
||||||
component -> StringBinaryTag.stringBinaryTag(GsonComponentSerializer.gson().serialize(component)));
|
component -> StringBinaryTag.stringBinaryTag(GsonComponentSerializer.gson().serialize(component)));
|
||||||
|
@ -11,7 +11,7 @@ import java.util.regex.Pattern;
|
|||||||
* An utilities class for {@link UUID}.
|
* An utilities class for {@link UUID}.
|
||||||
*/
|
*/
|
||||||
@ApiStatus.Internal
|
@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");
|
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
|
* @param input The input string to be checked
|
||||||
* @return {@code true} if the input an unique identifier, otherwise {@code false}
|
* @return {@code true} if the input an unique identifier, otherwise {@code false}
|
||||||
*/
|
*/
|
||||||
public static boolean isUniqueId(String input) {
|
public static boolean isUuid(String input) {
|
||||||
return input.matches(UNIQUE_ID_PATTERN.pattern());
|
return UNIQUE_ID_PATTERN.matcher(input).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull UUID fromNbt(@NotNull IntArrayBinaryTag tag) {
|
public static @NotNull UUID fromNbt(@NotNull IntArrayBinaryTag tag) {
|
@ -6,17 +6,28 @@ import org.jetbrains.annotations.ApiStatus;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to limit operations with recently operated chunks
|
||||||
|
* <p>
|
||||||
|
* {@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
|
@ApiStatus.Internal
|
||||||
public final class ChunkUpdateLimitChecker {
|
public final class ChunkUpdateLimitChecker {
|
||||||
|
|
||||||
private final int historySize;
|
private final int historySize;
|
||||||
private final long[] chunkHistory;
|
private final long[] chunkHistory;
|
||||||
|
|
||||||
public ChunkUpdateLimitChecker(int historySize) {
|
public ChunkUpdateLimitChecker(int historySize) {
|
||||||
this.historySize = historySize;
|
this.historySize = Math.max(0, historySize);
|
||||||
this.chunkHistory = new long[historySize];
|
this.chunkHistory = new long[this.historySize];
|
||||||
this.clearHistory();
|
this.clearHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return historySize > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the chunk to the history
|
* Adds the chunk to the history
|
||||||
*
|
*
|
||||||
@ -24,15 +35,20 @@ public final class ChunkUpdateLimitChecker {
|
|||||||
* @return {@code true} if it's a new chunk in the history
|
* @return {@code true} if it's a new chunk in the history
|
||||||
*/
|
*/
|
||||||
public boolean addToHistory(Chunk chunk) {
|
public boolean addToHistory(Chunk chunk) {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
final long index = CoordConversion.chunkIndex(chunk.getChunkX(), chunk.getChunkZ());
|
final long index = CoordConversion.chunkIndex(chunk.getChunkX(), chunk.getChunkZ());
|
||||||
boolean result = true;
|
boolean result = true;
|
||||||
final int lastIndex = historySize - 1;
|
final int lastIndex = historySize - 1;
|
||||||
for (int i = 0; i < lastIndex; i++) {
|
for (int i = 0; i <= lastIndex; i++) {
|
||||||
if (chunkHistory[i] == index) {
|
if (chunkHistory[i] == index) {
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
if (i != lastIndex) {
|
||||||
chunkHistory[i] = chunkHistory[i + 1];
|
chunkHistory[i] = chunkHistory[i + 1];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
chunkHistory[lastIndex] = index;
|
chunkHistory[lastIndex] = index;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import net.minestom.server.registry.DynamicRegistry;
|
|||||||
import net.minestom.server.registry.ProtocolObject;
|
import net.minestom.server.registry.ProtocolObject;
|
||||||
import net.minestom.server.registry.Registries;
|
import net.minestom.server.registry.Registries;
|
||||||
import net.minestom.server.utils.NamespaceID;
|
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.Unit;
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
@ -257,7 +257,7 @@ public interface BinaryTagSerializer<T> {
|
|||||||
BinaryTagSerializer<UUID> UUID = new BinaryTagSerializer<>() {
|
BinaryTagSerializer<UUID> UUID = new BinaryTagSerializer<>() {
|
||||||
@Override
|
@Override
|
||||||
public @NotNull BinaryTag write(java.util.@NotNull UUID value) {
|
public @NotNull BinaryTag write(java.util.@NotNull UUID value) {
|
||||||
return UniqueIdUtils.toNbt(value);
|
return UUIDUtils.toNbt(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -265,7 +265,7 @@ public interface BinaryTagSerializer<T> {
|
|||||||
if (!(tag instanceof IntArrayBinaryTag intArrayTag)) {
|
if (!(tag instanceof IntArrayBinaryTag intArrayTag)) {
|
||||||
throw new IllegalArgumentException("unexpected uuid type: " + tag.type());
|
throw new IllegalArgumentException("unexpected uuid type: " + tag.type());
|
||||||
}
|
}
|
||||||
return UniqueIdUtils.fromNbt(intArrayTag);
|
return UUIDUtils.fromNbt(intArrayTag);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,6 +8,11 @@ import net.minestom.testing.Env;
|
|||||||
import net.minestom.testing.EnvTest;
|
import net.minestom.testing.EnvTest;
|
||||||
import org.junit.jupiter.api.Test;
|
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;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
@EnvTest
|
@EnvTest
|
||||||
@ -99,4 +104,21 @@ public class EntityTeleportIntegrationTest {
|
|||||||
player.teleport(new Pos(5, 10, 2, 5, 5), null, RelativeFlags.VIEW).join();
|
player.teleport(new Pos(5, 10, 2, 5, 5), null, RelativeFlags.VIEW).join();
|
||||||
assertEquals(player.getPosition(), new Pos(5, 10, 2, 95, 5));
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,33 @@ public class EventNodeTest {
|
|||||||
assertTrue(result1.get(), "Recursive1 should be called due to the RecursiveEvent interface");
|
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
|
// FIXME: nodes are currently unable to retrieve sub handles
|
||||||
//@Test
|
//@Test
|
||||||
//public void recursiveSuper() {
|
//public void recursiveSuper() {
|
||||||
|
@ -99,7 +99,7 @@ public class InstanceUnregisterIntegrationTest {
|
|||||||
|
|
||||||
private void tmp(InstanceContainer instanceContainer) {
|
private void tmp(InstanceContainer instanceContainer) {
|
||||||
instanceContainer.eventNode().addListener(InstanceTickEvent.class, (e) -> {
|
instanceContainer.eventNode().addListener(InstanceTickEvent.class, (e) -> {
|
||||||
var uuid = instanceContainer.getUniqueId();
|
var uuid = instanceContainer.getUuid();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package net.minestom.server.item.component;
|
package net.minestom.server.item.component;
|
||||||
|
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.component.DataComponent;
|
import net.minestom.server.component.DataComponent;
|
||||||
import net.minestom.server.item.ItemComponent;
|
import net.minestom.server.item.ItemComponent;
|
||||||
import net.minestom.server.network.NetworkBuffer;
|
import net.minestom.server.network.NetworkBuffer;
|
||||||
@ -25,6 +26,10 @@ public class UnitTest extends AbstractItemComponentTest<Unit> {
|
|||||||
ItemComponent.GLIDER
|
ItemComponent.GLIDER
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static {
|
||||||
|
MinecraftServer.init();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected @NotNull DataComponent<Unit> component() {
|
protected @NotNull DataComponent<Unit> component() {
|
||||||
return UNIT_COMPONENTS.getFirst();
|
return UNIT_COMPONENTS.getFirst();
|
||||||
@ -46,7 +51,7 @@ public class UnitTest extends AbstractItemComponentTest<Unit> {
|
|||||||
// Try to write as a Unit and if it fails we can ignore that type
|
// Try to write as a Unit and if it fails we can ignore that type
|
||||||
try {
|
try {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
((DataComponent<Unit>) component).write(NetworkBuffer.resizableBuffer(), Unit.INSTANCE);
|
((DataComponent<Unit>) component).write(NetworkBuffer.resizableBuffer(MinecraftServer.process()), Unit.INSTANCE);
|
||||||
} catch (ClassCastException ignored) {
|
} catch (ClassCastException ignored) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,8 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.jetbrains.annotations.UnknownNullability;
|
import org.jetbrains.annotations.UnknownNullability;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UTFDataFormatException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@ -474,6 +476,62 @@ public class NetworkBufferTest {
|
|||||||
assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING)); // oom
|
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 <T> void assertBufferType(NetworkBuffer.@NotNull Type<T> type, @UnknownNullability T value, byte[] expected, @NotNull Action<T> action) {
|
static <T> void assertBufferType(NetworkBuffer.@NotNull Type<T> type, @UnknownNullability T value, byte[] expected, @NotNull Action<T> action) {
|
||||||
var buffer = NetworkBuffer.resizableBuffer(MinecraftServer.process());
|
var buffer = NetworkBuffer.resizableBuffer(MinecraftServer.process());
|
||||||
action.write(buffer, type, value);
|
action.write(buffer, type, value);
|
||||||
|
34
src/test/java/net/minestom/server/utils/UUIDUtilsTest.java
Normal file
34
src/test/java/net/minestom/server/utils/UUIDUtilsTest.java
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user