Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-19 14:30:17 +01:00
Merge branch 'master' into main
Dieser Commit ist enthalten in:
Commit
c9b27c3987
14
.editorconfig
Normale Datei
14
.editorconfig
Normale Datei
@ -0,0 +1,14 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
tab_width = 4
|
||||
max_line_length = off
|
||||
|
||||
[*.java]
|
||||
ij_java_class_count_to_use_import_on_demand = 9999
|
||||
ij_java_doc_align_exception_comments = false
|
||||
ij_java_doc_align_param_comments = false
|
89
.github/workflows/build-remote.yml
vendored
89
.github/workflows/build-remote.yml
vendored
@ -22,81 +22,26 @@ jobs:
|
||||
run: |
|
||||
echo "BUILD_NUMBER=${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up JDK 21
|
||||
# See https://github.com/actions/setup-java/commits
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
- name: Setup Gradle
|
||||
uses: GeyserMC/actions/setup-gradle-composite@master
|
||||
with:
|
||||
java-version: 21
|
||||
distribution: temurin
|
||||
|
||||
- name: Checkout repository and submodules
|
||||
# See https://github.com/actions/checkout/commits
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
repository: ${{ inputs.repository }}
|
||||
ref: ${{ inputs.ref }}
|
||||
submodules: recursive
|
||||
path: geyser
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
# See https://github.com/gradle/wrapper-validation-action/commits
|
||||
uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2 # v2.1.1
|
||||
checkout_repository: ${{ inputs.repository }}
|
||||
checkout_ref: ${{ inputs.ref }}
|
||||
setup-java_java-version: 21
|
||||
setup-gradle_cache-read-only: true
|
||||
|
||||
- name: Build Geyser
|
||||
# See https://github.com/gradle/actions/commits
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
|
||||
with:
|
||||
arguments: build
|
||||
build-root-directory: geyser
|
||||
cache-read-only: true
|
||||
run: ./gradlew build
|
||||
|
||||
- name: Archive artifacts (Geyser Fabric)
|
||||
# See https://github.com/actions/upload-artifact/commits
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
- name: Archive Artifacts
|
||||
uses: GeyserMC/actions/upload-multi-artifact@master
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser Fabric
|
||||
path: geyser/bootstrap/mod/fabric/build/libs/Geyser-Fabric.jar
|
||||
if-no-files-found: error
|
||||
- name: Archive artifacts (Geyser NeoForge)
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser NeoForge
|
||||
path: geyser/bootstrap/mod/neoforge/build/libs/Geyser-NeoForge.jar
|
||||
if-no-files-found: error
|
||||
- name: Archive artifacts (Geyser Standalone)
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser Standalone
|
||||
path: geyser/bootstrap/standalone/build/libs/Geyser-Standalone.jar
|
||||
if-no-files-found: error
|
||||
- name: Archive artifacts (Geyser Spigot)
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser Spigot
|
||||
path: geyser/bootstrap/spigot/build/libs/Geyser-Spigot.jar
|
||||
if-no-files-found: error
|
||||
- name: Archive artifacts (Geyser BungeeCord)
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser BungeeCord
|
||||
path: geyser/bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar
|
||||
if-no-files-found: error
|
||||
- name: Archive artifacts (Geyser Velocity)
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser Velocity
|
||||
path: geyser/bootstrap/velocity/build/libs/Geyser-Velocity.jar
|
||||
if-no-files-found: error
|
||||
- name: Archive artifacts (Geyser ViaProxy)
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser ViaProxy
|
||||
path: geyser/bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar
|
||||
if-no-files-found: error
|
||||
artifacts: |
|
||||
bootstrap/mod/fabric/build/libs/Geyser-Fabric.jar
|
||||
bootstrap/mod/neoforge/build/libs/Geyser-NeoForge.jar
|
||||
bootstrap/standalone/build/libs/Geyser-Standalone.jar
|
||||
bootstrap/spigot/build/libs/Geyser-Spigot.jar
|
||||
bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar
|
||||
bootstrap/velocity/build/libs/Geyser-Velocity.jar
|
||||
bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar
|
192
.github/workflows/build.yml
vendored
192
.github/workflows/build.yml
vendored
@ -21,103 +21,55 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PROJECT: 'geyser'
|
||||
steps:
|
||||
- name: Set Build Number
|
||||
- name: Get Release Info
|
||||
id: release-info
|
||||
uses: GeyserMC/actions/previous-release@master
|
||||
with:
|
||||
data: ${{ vars.RELEASEACTION_PREVRELEASE }}
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: GeyserMC/actions/setup-gradle-composite@master
|
||||
with:
|
||||
setup-java_java-version: 21
|
||||
|
||||
- name: Build Geyser
|
||||
run: ./gradlew build
|
||||
env:
|
||||
BUILD_JSON: ${{ vars.RELEASEACTION_PREVRELEASE }}
|
||||
run: |
|
||||
BUILD_NUMBER=$(echo $BUILD_JSON | jq --arg branch "${GITHUB_REF_NAME}" 'if .[$branch] == null then 1 else .[$branch] | .t | tonumber + 1 end // 1')
|
||||
echo "BUILD_NUMBER=${BUILD_NUMBER:=$GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
BUILD_NUMBER: ${{ steps.release-info.outputs.curentRelease }}
|
||||
|
||||
- name: Checkout repository and submodules
|
||||
# See https://github.com/actions/checkout/commits
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
# See https://github.com/gradle/wrapper-validation-action/commits
|
||||
uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2 # v2.1.1
|
||||
|
||||
# See https://github.com/actions/setup-java/commits
|
||||
- uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
with:
|
||||
java-version: 21
|
||||
distribution: temurin
|
||||
|
||||
- name: Build
|
||||
# See https://github.com/gradle/actions/commits
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
|
||||
with:
|
||||
arguments: build
|
||||
gradle-home-cache-cleanup: true
|
||||
|
||||
- name: Archive artifacts (Geyser Fabric)
|
||||
# See https://github.com/actions/upload-artifact/commits
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
- name: Archive Artifacts
|
||||
uses: GeyserMC/actions/upload-multi-artifact@master
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser Fabric
|
||||
path: bootstrap/mod/fabric/build/libs/Geyser-Fabric.jar
|
||||
if-no-files-found: error
|
||||
- name: Archive artifacts (Geyser NeoForge)
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser NeoForge
|
||||
path: bootstrap/mod/neoforge/build/libs/Geyser-NeoForge.jar
|
||||
if-no-files-found: error
|
||||
- name: Archive artifacts (Geyser Standalone)
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser Standalone
|
||||
path: bootstrap/standalone/build/libs/Geyser-Standalone.jar
|
||||
if-no-files-found: error
|
||||
- name: Archive artifacts (Geyser Spigot)
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser Spigot
|
||||
path: bootstrap/spigot/build/libs/Geyser-Spigot.jar
|
||||
if-no-files-found: error
|
||||
- name: Archive artifacts (Geyser BungeeCord)
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser BungeeCord
|
||||
path: bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar
|
||||
if-no-files-found: error
|
||||
- name: Archive artifacts (Geyser Velocity)
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser Velocity
|
||||
path: bootstrap/velocity/build/libs/Geyser-Velocity.jar
|
||||
if-no-files-found: error
|
||||
- name: Archive artifacts (Geyser ViaProxy)
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3
|
||||
if: success()
|
||||
with:
|
||||
name: Geyser ViaProxy
|
||||
path: bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar
|
||||
if-no-files-found: error
|
||||
artifacts: |
|
||||
bootstrap/mod/fabric/build/libs/Geyser-Fabric.jar
|
||||
bootstrap/mod/neoforge/build/libs/Geyser-NeoForge.jar
|
||||
bootstrap/standalone/build/libs/Geyser-Standalone.jar
|
||||
bootstrap/spigot/build/libs/Geyser-Spigot.jar
|
||||
bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar
|
||||
bootstrap/velocity/build/libs/Geyser-Velocity.jar
|
||||
bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar
|
||||
|
||||
- name: Publish to Maven Repository
|
||||
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
run: ./gradlew publish
|
||||
env:
|
||||
BUILD_NUMBER: ${{ steps.release-info.outputs.curentRelease }}
|
||||
ORG_GRADLE_PROJECT_geysermcUsername: ${{ vars.DEPLOY_USER }}
|
||||
ORG_GRADLE_PROJECT_geysermcPassword: ${{ secrets.DEPLOY_PASS }}
|
||||
with:
|
||||
arguments: publish
|
||||
|
||||
- name: Get Version
|
||||
if: ${{ (success() || failure()) && github.repository == 'GeyserMC/Geyser' }}
|
||||
id: get-version
|
||||
run: |
|
||||
version=$(cat gradle.properties | grep -o "version=[0-9\\.]*" | cut -d"=" -f2)
|
||||
echo "VERSION=${version}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get Release Metadata
|
||||
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
|
||||
# See https://github.com/Kas-tle/base-release-action/releases/tag/main-11
|
||||
uses: Kas-tle/base-release-action@b863fa0f89bd15267a96a72efb84aec25f168d4c # main-11
|
||||
if: ${{ (success() || failure()) && github.repository == 'GeyserMC/Geyser' }}
|
||||
uses: GeyserMC/actions/release@master
|
||||
id: metadata
|
||||
with:
|
||||
appID: ${{ secrets.RELEASE_APP_ID }}
|
||||
appPrivateKey: ${{ secrets.RELEASE_APP_PK }}
|
||||
@ -131,61 +83,39 @@ jobs:
|
||||
viaproxy:bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar
|
||||
releaseEnabled: false
|
||||
saveMetadata: true
|
||||
- name: Update Generated Metadata
|
||||
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
|
||||
run: |
|
||||
cat metadata.json
|
||||
echo
|
||||
mv metadata.json metadata.json.tmp
|
||||
version=$(cat gradle.properties | grep -o "version=[0-9\\.]*" | cut -d"=" -f2)
|
||||
jq --arg project "${PROJECT}" --arg version "${version}" '
|
||||
.
|
||||
| .changes |= map({"commit", "summary", "message"})
|
||||
| .downloads |= map_values({"name", "sha256"})
|
||||
| {$project, "repo", $version, "number": .build, "changes", "downloads"}
|
||||
' metadata.json.tmp > metadata.json
|
||||
cat metadata.json
|
||||
releaseProject: 'geyser'
|
||||
releaseVersion: ${{ steps.get-version.outputs.VERSION }}
|
||||
|
||||
- name: Publish to Downloads API
|
||||
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
|
||||
shell: bash
|
||||
env:
|
||||
DOWNLOADS_USERNAME: ${{ vars.DOWNLOADS_USERNAME }}
|
||||
DOWNLOADS_PRIVATE_KEY: ${{ secrets.DOWNLOADS_PRIVATE_KEY }}
|
||||
DOWNLOADS_SERVER_IP: ${{ secrets.DOWNLOADS_SERVER_IP }}
|
||||
run: |
|
||||
# Save the private key to a file
|
||||
echo "$DOWNLOADS_PRIVATE_KEY" > id_ecdsa
|
||||
chmod 600 id_ecdsa
|
||||
# Create the build folder
|
||||
ssh -o StrictHostKeyChecking=no -i id_ecdsa $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP mkdir -p "~/uploads/$PROJECT/$GITHUB_RUN_NUMBER/"
|
||||
# Copy over artifacts
|
||||
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" bootstrap/**/build/libs/Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$GITHUB_RUN_NUMBER/
|
||||
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" bootstrap/mod/**/build/libs/Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$GITHUB_RUN_NUMBER/
|
||||
# Run the build script
|
||||
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" metadata.json $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$GITHUB_RUN_NUMBER/
|
||||
uses: GeyserMC/actions/upload-release@master
|
||||
with:
|
||||
username: ${{ vars.DOWNLOADS_USERNAME }}
|
||||
privateKey: ${{ secrets.DOWNLOADS_PRIVATE_KEY }}
|
||||
host: ${{ secrets.DOWNLOADS_SERVER_IP }}
|
||||
files: |
|
||||
bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar
|
||||
bootstrap/mod/fabric/build/libs/Geyser-Fabric.jar
|
||||
bootstrap/mod/neoforge/build/libs/Geyser-NeoForge.jar
|
||||
bootstrap/spigot/build/libs/Geyser-Spigot.jar
|
||||
bootstrap/standalone/build/libs/Geyser-Standalone.jar
|
||||
bootstrap/velocity/build/libs/Geyser-Velocity.jar
|
||||
bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar
|
||||
changelog: ${{ steps.metadata.outputs.body }}
|
||||
|
||||
- name: Publish to Modrinth (Fabric)
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
- name: Publish to Modrinth
|
||||
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
|
||||
env:
|
||||
CHANGELOG: ${{ steps.metadata.outputs.body }}
|
||||
BUILD_NUMBER: ${{ steps.release-info.outputs.curentRelease }}
|
||||
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
|
||||
with:
|
||||
arguments: fabric:modrinth
|
||||
gradle-home-cache-cleanup: true
|
||||
|
||||
- name: Publish to Modrinth (NeoForge)
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
|
||||
env:
|
||||
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
|
||||
with:
|
||||
arguments: neoforge:modrinth
|
||||
gradle-home-cache-cleanup: true
|
||||
run: ./gradlew modrinth
|
||||
|
||||
- name: Notify Discord
|
||||
if: ${{ (success() || failure()) && github.repository == 'GeyserMC/Geyser' }}
|
||||
# See https://github.com/Tim203/actions-git-discord-webhook/commits
|
||||
uses: Tim203/actions-git-discord-webhook@70f38ded3aca51635ec978ab4e1a58cd4cd0c2ff
|
||||
uses: GeyserMC/actions/notify-discord@master
|
||||
with:
|
||||
webhook_url: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
discordWebhook: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
status: ${{ job.status }}
|
||||
body: ${{ steps.metadata.outputs.body }}
|
||||
includeDownloads: ${{ github.ref_name == 'master' }}
|
||||
|
33
.github/workflows/dispatch-preview.yml
vendored
Normale Datei
33
.github/workflows/dispatch-preview.yml
vendored
Normale Datei
@ -0,0 +1,33 @@
|
||||
name: Dispatch Preview
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
runId:
|
||||
required: true
|
||||
description: 'ID of the action to pull artifacts from'
|
||||
build:
|
||||
required: true
|
||||
description: 'Build number for the release'
|
||||
version:
|
||||
required: true
|
||||
description: 'Version under which to upload to the Downloads API'
|
||||
|
||||
jobs:
|
||||
dispatch-preview:
|
||||
# Allow access to secrets if we are uploading a preview
|
||||
secrets: inherit
|
||||
uses: GeyserMC/actions/.github/workflows/upload-preview.yml@master
|
||||
with:
|
||||
build: ${{ inputs.build }}
|
||||
version: ${{ inputs.version }}
|
||||
files: |
|
||||
bungeecord:Geyser-BungeeCord.jar
|
||||
fabric:Geyser-Fabric.jar
|
||||
neoforge:Geyser-NeoForge.jar
|
||||
spigot:Geyser-Spigot.jar
|
||||
standalone:Geyser-Standalone.jar
|
||||
velocity:Geyser-Velocity.jar
|
||||
viaproxy:Geyser-ViaProxy.jar
|
||||
project: geyserpreview
|
||||
runId: ${{ inputs.runId }}
|
96
.github/workflows/preview.yml
vendored
96
.github/workflows/preview.yml
vendored
@ -1,96 +0,0 @@
|
||||
name: Upload Preview
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
runId:
|
||||
required: true
|
||||
description: 'ID of the action to pull artifacts from'
|
||||
build:
|
||||
required: true
|
||||
description: 'Build number for the release'
|
||||
version:
|
||||
required: true
|
||||
description: 'Version under which to upload to the Downloads API'
|
||||
workflow_call:
|
||||
inputs:
|
||||
build:
|
||||
required: true
|
||||
description: 'Build number for the release'
|
||||
type: string
|
||||
version:
|
||||
required: true
|
||||
description: 'Version under which to upload to the Downloads API'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PROJECT: 'geyserpreview'
|
||||
steps:
|
||||
- name: Set Variables
|
||||
id: setvars
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||
echo "BUILD=${{ github.event.inputs.build }}" >> $GITHUB_ENV
|
||||
echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
|
||||
echo "RUN=${{ github.event.inputs.runId }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "BUILD=${{ inputs.build }}" >> $GITHUB_ENV
|
||||
echo "VERSION=${{ inputs.version }}" >> $GITHUB_ENV
|
||||
echo "RUN=${{ github.run_id }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427
|
||||
with:
|
||||
run-id: ${{ steps.setvars.outputs.RUN }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
merge-multiple: true
|
||||
- name: Get Preview Metadata
|
||||
if: success()
|
||||
# See https://github.com/Kas-tle/base-release-action/releases/tag/main-11
|
||||
uses: Kas-tle/base-release-action@664c39985eb9d0d393ce98e7eb8414d3d98e762a # main-11
|
||||
with:
|
||||
appID: ${{ secrets.RELEASE_APP_ID }}
|
||||
appPrivateKey: ${{ secrets.RELEASE_APP_PK }}
|
||||
files: |
|
||||
bungeecord:Geyser-BungeeCord.jar
|
||||
fabric:Geyser-Fabric.jar
|
||||
neoforge:Geyser-NeoForge.jar
|
||||
spigot:Geyser-Spigot.jar
|
||||
standalone:Geyser-Standalone.jar
|
||||
velocity:Geyser-Velocity.jar
|
||||
viaproxy:Geyser-ViaProxy.jar
|
||||
releaseEnabled: false
|
||||
saveMetadata: true
|
||||
updateReleaseData: false
|
||||
- name: Update Generated Metadata
|
||||
if: success()
|
||||
run: |
|
||||
cat metadata.json
|
||||
echo
|
||||
mv metadata.json metadata.json.tmp
|
||||
jq --arg project "${PROJECT}" --arg version "${VERSION}" --arg number "${BUILD}" '
|
||||
.
|
||||
| .downloads |= map_values({"name", "sha256"})
|
||||
| {$project, "repo", $version, "number": $number | tonumber, "changes": [], "downloads"}
|
||||
' metadata.json.tmp > metadata.json
|
||||
cat metadata.json
|
||||
- name: Publish to Downloads API
|
||||
if: success()
|
||||
shell: bash
|
||||
env:
|
||||
DOWNLOADS_USERNAME: ${{ vars.DOWNLOADS_USERNAME }}
|
||||
DOWNLOADS_PRIVATE_KEY: ${{ secrets.DOWNLOADS_PRIVATE_KEY }}
|
||||
DOWNLOADS_SERVER_IP: ${{ secrets.DOWNLOADS_SERVER_IP }}
|
||||
run: |
|
||||
# Save the private key to a file
|
||||
echo "$DOWNLOADS_PRIVATE_KEY" > id_ecdsa
|
||||
chmod 600 id_ecdsa
|
||||
# Create the build folder
|
||||
ssh -o StrictHostKeyChecking=no -i id_ecdsa $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP mkdir -p "~/uploads/$PROJECT/$BUILD/"
|
||||
# Copy over artifacts
|
||||
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$BUILD/
|
||||
# Run the build script
|
||||
# Push the metadata
|
||||
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" metadata.json $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$BUILD/
|
14
.github/workflows/pull-request.yml
vendored
14
.github/workflows/pull-request.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
||||
# Forbid access to secrets nor GH Token perms while building the PR
|
||||
permissions: {}
|
||||
secrets: {}
|
||||
uses: ./.github/workflows/build-remote.yml
|
||||
uses: GeyserMC/Geyser/.github/workflows/build-remote.yml@master
|
||||
with:
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
@ -18,7 +18,17 @@ jobs:
|
||||
contains(github.event.pull_request.labels.*.name, 'PR: Needs Testing')
|
||||
# Allow access to secrets if we are uploading a preview
|
||||
secrets: inherit
|
||||
uses: ./.github/workflows/preview.yml
|
||||
uses: GeyserMC/actions/.github/workflows/upload-preview.yml@master
|
||||
with:
|
||||
build: ${{ github.run_number }}
|
||||
version: pr.${{ github.event.pull_request.number }}
|
||||
files: |
|
||||
bungeecord:Geyser-BungeeCord.jar
|
||||
fabric:Geyser-Fabric.jar
|
||||
neoforge:Geyser-NeoForge.jar
|
||||
spigot:Geyser-Spigot.jar
|
||||
standalone:Geyser-Standalone.jar
|
||||
velocity:Geyser-Velocity.jar
|
||||
viaproxy:Geyser-ViaProxy.jar
|
||||
project: geyserpreview
|
||||
runId: ${{ github.run_id }}
|
@ -1,6 +1,7 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value="Copyright (c) 2019-&#36;today.year GeyserMC. http://geysermc.org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @author GeyserMC @link https://github.com/GeyserMC/Geyser" />
|
||||
<option name="allowReplaceRegexp" value="Copyright" />
|
||||
<option name="notice" value="Copyright (c) &#36;originalComment.match("Copyright \(c\) (\d+)", 1, "-")&#36;today.year GeyserMC. http://geysermc.org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @author GeyserMC @link https://github.com/GeyserMC/Geyser" />
|
||||
<option name="myName" value="Geyser" />
|
||||
</copyright>
|
||||
</component>
|
@ -14,7 +14,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
|
||||
|
||||
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
||||
|
||||
### Currently supporting Minecraft Bedrock 1.20.40 - 1.20.80/81 and Minecraft Java 1.20.5/1.20.6
|
||||
### Currently supporting Minecraft Bedrock 1.20.80 - 1.21.1 and Minecraft Java 1.21
|
||||
|
||||
## Setting Up
|
||||
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
|
||||
|
@ -145,4 +145,36 @@ public interface CameraData {
|
||||
* @return whether the camera is currently locked
|
||||
*/
|
||||
boolean isCameraLocked();
|
||||
|
||||
/**
|
||||
* Hides a {@link GuiElement} on the client's side.
|
||||
*
|
||||
* @param element the {@link GuiElement} to hide
|
||||
*/
|
||||
void hideElement(@NonNull GuiElement... element);
|
||||
|
||||
/**
|
||||
* Resets a {@link GuiElement} on the client's side.
|
||||
* This makes the client decide on its own - e.g. based on client settings -
|
||||
* whether to show or hide the gui element.
|
||||
* <p>
|
||||
* If no elements are specified, this will reset all currently hidden elements
|
||||
*
|
||||
* @param element the {@link GuiElement} to reset
|
||||
*/
|
||||
void resetElement(@NonNull GuiElement @Nullable... element);
|
||||
|
||||
/**
|
||||
* Determines whether a {@link GuiElement} is currently hidden.
|
||||
*
|
||||
* @param element the {@link GuiElement} to check
|
||||
*/
|
||||
boolean isHudElementHidden(@NonNull GuiElement element);
|
||||
|
||||
/**
|
||||
* Returns the currently hidden {@link GuiElement}s.
|
||||
*
|
||||
* @return an unmodifiable view of all currently hidden {@link GuiElement}s
|
||||
*/
|
||||
@NonNull Set<GuiElement> hiddenElements();
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.api.bedrock.camera;
|
||||
|
||||
/**
|
||||
* Represent GUI elements on the players HUD display.
|
||||
* These can be hidden using {@link CameraData#hideElement(GuiElement...)},
|
||||
* and one can reset their visibility using {@link CameraData#resetElement(GuiElement...)}.
|
||||
*/
|
||||
public class GuiElement {
|
||||
public static final GuiElement PAPER_DOLL = new GuiElement(0);
|
||||
public static final GuiElement ARMOR = new GuiElement(1);
|
||||
public static final GuiElement TOOL_TIPS = new GuiElement(2);
|
||||
public static final GuiElement TOUCH_CONTROLS = new GuiElement(3);
|
||||
public static final GuiElement CROSSHAIR = new GuiElement(4);
|
||||
public static final GuiElement HOTBAR = new GuiElement(5);
|
||||
public static final GuiElement HEALTH = new GuiElement(6);
|
||||
public static final GuiElement PROGRESS_BAR = new GuiElement(7);
|
||||
public static final GuiElement FOOD_BAR = new GuiElement(8);
|
||||
public static final GuiElement AIR_BUBBLES_BAR = new GuiElement(9);
|
||||
public static final GuiElement VEHICLE_HEALTH = new GuiElement(10);
|
||||
public static final GuiElement EFFECTS_BAR = new GuiElement(11);
|
||||
public static final GuiElement ITEM_TEXT_POPUP = new GuiElement(12);
|
||||
|
||||
private GuiElement(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
private final int id;
|
||||
|
||||
/**
|
||||
* Internal use only; don't depend on these values being consistent.
|
||||
*/
|
||||
public int id() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
@ -73,7 +73,10 @@ public interface JavaBlockState {
|
||||
* Gets whether the block state has block entity
|
||||
*
|
||||
* @return whether the block state has block entity
|
||||
* @deprecated Does not have an effect. If you were using this to
|
||||
* set piston behavior, use {@link #pistonBehavior()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
boolean hasBlockEntity();
|
||||
|
||||
/**
|
||||
@ -104,6 +107,11 @@ public interface JavaBlockState {
|
||||
|
||||
Builder pistonBehavior(@Nullable String pistonBehavior);
|
||||
|
||||
/**
|
||||
* @deprecated Does not have an effect. If you were using this to
|
||||
* * set piston behavior, use {@link #pistonBehavior(String)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
Builder hasBlockEntity(boolean hasBlockEntity);
|
||||
|
||||
JavaBlockState build();
|
||||
|
@ -36,7 +36,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Called when a session has logged in, and is about to connect to a remote java server.
|
||||
* Called when a session has logged in, and is about to connect to a remote Java server.
|
||||
* This event is cancellable, and can be used to prevent the player from connecting to the remote server.
|
||||
*/
|
||||
public final class SessionLoginEvent extends ConnectionEvent implements Cancellable {
|
||||
@ -99,9 +99,9 @@ public final class SessionLoginEvent extends ConnectionEvent implements Cancella
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link RemoteServer} the section will attempt to connect to.
|
||||
* Gets the {@link RemoteServer} the session will attempt to connect to.
|
||||
*
|
||||
* @return the {@link RemoteServer} the section will attempt to connect to.
|
||||
* @return the {@link RemoteServer} the session will attempt to connect to.
|
||||
*/
|
||||
public @NonNull RemoteServer remoteServer() {
|
||||
return this.remoteServer;
|
||||
|
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.api.event.bedrock;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.connection.GeyserConnection;
|
||||
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
|
||||
import org.geysermc.geyser.api.skin.Cape;
|
||||
import org.geysermc.geyser.api.skin.Skin;
|
||||
import org.geysermc.geyser.api.skin.SkinData;
|
||||
import org.geysermc.geyser.api.skin.SkinGeometry;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Called when a skin is applied to a player.
|
||||
* <p>
|
||||
* Won't be called when a fake player is spawned for a player skull.
|
||||
*/
|
||||
public abstract class SessionSkinApplyEvent extends ConnectionEvent {
|
||||
|
||||
private final String username;
|
||||
private final UUID uuid;
|
||||
private final boolean slim;
|
||||
private final boolean bedrock;
|
||||
private final SkinData originalSkinData;
|
||||
|
||||
public SessionSkinApplyEvent(@NonNull GeyserConnection connection, String username, UUID uuid, boolean slim, boolean bedrock, SkinData skinData) {
|
||||
super(connection);
|
||||
this.username = username;
|
||||
this.uuid = uuid;
|
||||
this.slim = slim;
|
||||
this.bedrock = bedrock;
|
||||
this.originalSkinData = skinData;
|
||||
}
|
||||
|
||||
/**
|
||||
* The username of the player.
|
||||
*
|
||||
* @return the username of the player
|
||||
*/
|
||||
public @NonNull String username() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* The UUID of the player.
|
||||
*
|
||||
* @return the UUID of the player
|
||||
*/
|
||||
public @NonNull UUID uuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the player is using a slim model.
|
||||
*
|
||||
* @return if the player is using a slim model
|
||||
*/
|
||||
public boolean slim() {
|
||||
return slim;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the player is a Bedrock player.
|
||||
*
|
||||
* @return if the player is a Bedrock player
|
||||
*/
|
||||
public boolean bedrock() {
|
||||
return bedrock;
|
||||
}
|
||||
|
||||
/**
|
||||
* The original skin data of the player.
|
||||
*
|
||||
* @return the original skin data of the player
|
||||
*/
|
||||
public @NonNull SkinData originalSkin() {
|
||||
return originalSkinData;
|
||||
}
|
||||
|
||||
/**
|
||||
* The skin data of the player.
|
||||
*
|
||||
* @return the current skin data of the player
|
||||
*/
|
||||
public abstract @NonNull SkinData skinData();
|
||||
|
||||
/**
|
||||
* Change the skin of the player.
|
||||
*
|
||||
* @param newSkin the new skin
|
||||
*/
|
||||
public abstract void skin(@NonNull Skin newSkin);
|
||||
|
||||
/**
|
||||
* Change the cape of the player.
|
||||
*
|
||||
* @param newCape the new cape
|
||||
*/
|
||||
public abstract void cape(@NonNull Cape newCape);
|
||||
|
||||
/**
|
||||
* Change the geometry of the player.
|
||||
*
|
||||
* @param newGeometry the new geometry
|
||||
*/
|
||||
public abstract void geometry(@NonNull SkinGeometry newGeometry);
|
||||
|
||||
/**
|
||||
* Change the geometry of the player.
|
||||
* <p>
|
||||
* Constructs a generic {@link SkinGeometry} object with the given data.
|
||||
*
|
||||
* @param geometryName the name of the geometry
|
||||
* @param geometryData the data of the geometry
|
||||
*/
|
||||
public void geometry(@NonNull String geometryName, @NonNull String geometryData) {
|
||||
geometry(new SkinGeometry("{\"geometry\" :{\"default\" :\"" + geometryName + "\"}}", geometryData));
|
||||
}
|
||||
}
|
@ -46,13 +46,35 @@ public final class ConnectionRequestEvent implements Event, Cancellable {
|
||||
this.proxyIp = proxyIp;
|
||||
}
|
||||
|
||||
/**
|
||||
* The IP address of the client attempting to connect
|
||||
*
|
||||
* @return the IP address of the client attempting to connect
|
||||
* @deprecated Use {@link #inetSocketAddress()} instead
|
||||
*/
|
||||
@NonNull @Deprecated(forRemoval = true)
|
||||
public InetSocketAddress getInetSocketAddress() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* The IP address of the proxy handling the connection. It will return null if there is no proxy.
|
||||
*
|
||||
* @return the IP address of the proxy handling the connection
|
||||
* @deprecated Use {@link #proxyIp()} instead
|
||||
*/
|
||||
@Nullable @Deprecated(forRemoval = true)
|
||||
public InetSocketAddress getProxyIp() {
|
||||
return proxyIp;
|
||||
}
|
||||
|
||||
/**
|
||||
* The IP address of the client attempting to connect
|
||||
*
|
||||
* @return the IP address of the client attempting to connect
|
||||
*/
|
||||
@NonNull
|
||||
public InetSocketAddress getInetSocketAddress() {
|
||||
public InetSocketAddress inetSocketAddress() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
@ -62,7 +84,7 @@ public final class ConnectionRequestEvent implements Event, Cancellable {
|
||||
* @return the IP address of the proxy handling the connection
|
||||
*/
|
||||
@Nullable
|
||||
public InetSocketAddress getProxyIp() {
|
||||
public InetSocketAddress proxyIp() {
|
||||
return proxyIp;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -23,12 +23,18 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.api.skin;
|
||||
|
||||
/**
|
||||
* Contains useful collections for use in Geyser.
|
||||
* <p>
|
||||
* Of note are the fixed int maps. Designed for use with block states that are positive and sequential, they do not allow keys to be
|
||||
* added that are not greater by one versus the previous key. Because of this, speedy operations of {@link java.util.Map#get(java.lang.Object)}
|
||||
* and {@link java.util.Map#containsKey(java.lang.Object)} can be performed by simply checking the bounds of the map
|
||||
* size and its "start" integer.
|
||||
* Represents a cape.
|
||||
*
|
||||
* @param textureUrl The URL of the cape texture
|
||||
* @param capeId The ID of the cape
|
||||
* @param capeData The raw cape image data in ARGB format
|
||||
* @param failed If the cape failed to load, this is for things like fallback capes
|
||||
*/
|
||||
package org.geysermc.geyser.util.collection;
|
||||
public record Cape(String textureUrl, String capeId, byte[] capeData, boolean failed) {
|
||||
public Cape(String textureUrl, String capeId, byte[] capeData) {
|
||||
this(textureUrl, capeId, capeData, false);
|
||||
}
|
||||
}
|
39
api/src/main/java/org/geysermc/geyser/api/skin/Skin.java
Normale Datei
39
api/src/main/java/org/geysermc/geyser/api/skin/Skin.java
Normale Datei
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.api.skin;
|
||||
|
||||
/**
|
||||
* Represents a skin.
|
||||
*
|
||||
* @param textureUrl The URL/ID of the skin texture
|
||||
* @param skinData The raw skin image data in ARGB
|
||||
* @param failed If the skin failed to load, this is for things like fallback skins
|
||||
*/
|
||||
public record Skin(String textureUrl, byte[] skinData, boolean failed) {
|
||||
public Skin(String textureUrl, byte[] skinData) {
|
||||
this(textureUrl, skinData, false);
|
||||
}
|
||||
}
|
32
api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java
Normale Datei
32
api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java
Normale Datei
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.api.skin;
|
||||
|
||||
/**
|
||||
* Represents a full package of {@link Skin}, {@link Cape}, and {@link SkinGeometry}.
|
||||
*/
|
||||
public record SkinData(Skin skin, Cape cape, SkinGeometry geometry) {
|
||||
}
|
48
api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java
Normale Datei
48
api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java
Normale Datei
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.api.skin;
|
||||
|
||||
/**
|
||||
* Represents geometry of a skin.
|
||||
*
|
||||
* @param geometryName The name of the geometry (JSON)
|
||||
* @param geometryData The geometry data (JSON)
|
||||
*/
|
||||
public record SkinGeometry(String geometryName, String geometryData) {
|
||||
|
||||
public static SkinGeometry WIDE = getLegacy(false);
|
||||
public static SkinGeometry SLIM = getLegacy(true);
|
||||
|
||||
/**
|
||||
* Generate generic geometry
|
||||
*
|
||||
* @param isSlim if true, it will be the slimmer alex model
|
||||
* @return The generic geometry object
|
||||
*/
|
||||
private static SkinGeometry getLegacy(boolean isSlim) {
|
||||
return new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.custom" + (isSlim ? "Slim" : "") + "\"}}", "");
|
||||
}
|
||||
}
|
@ -34,3 +34,8 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
exclude(dependency("io.netty:netty-resolver-dns:.*"))
|
||||
}
|
||||
}
|
||||
|
||||
modrinth {
|
||||
uploadFile.set(tasks.getByPath("shadowJar"))
|
||||
loaders.add("bungeecord")
|
||||
}
|
||||
|
@ -26,22 +26,19 @@
|
||||
package org.geysermc.geyser.platform.bungeecord;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class GeyserBungeeLogger implements GeyserLogger {
|
||||
private final Logger logger;
|
||||
@Getter @Setter
|
||||
private boolean debug;
|
||||
|
||||
public GeyserBungeeLogger(Logger logger, boolean debug) {
|
||||
this.logger = logger;
|
||||
this.debug = debug;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String message) {
|
||||
logger.severe(message);
|
||||
|
@ -58,14 +58,13 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
|
||||
private GeyserCommandManager geyserCommandManager;
|
||||
private GeyserBungeeConfiguration geyserConfig;
|
||||
private GeyserBungeeInjector geyserInjector;
|
||||
private GeyserBungeeLogger geyserLogger;
|
||||
private final GeyserBungeeLogger geyserLogger = new GeyserBungeeLogger(getLogger());
|
||||
private IGeyserPingPassthrough geyserBungeePingPassthrough;
|
||||
|
||||
private GeyserImpl geyser;
|
||||
@ -82,21 +81,21 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
// Copied from ViaVersion.
|
||||
// https://github.com/ViaVersion/ViaVersion/blob/b8072aad86695cc8ec6f5e4103e43baf3abf6cc5/bungee/src/main/java/us/myles/ViaVersion/BungeePlugin.java#L43
|
||||
try {
|
||||
ProtocolConstants.class.getField("MINECRAFT_1_20_3");
|
||||
ProtocolConstants.class.getField("MINECRAFT_1_21");
|
||||
} catch (NoSuchFieldException e) {
|
||||
getLogger().warning(" / \\");
|
||||
getLogger().warning(" / \\");
|
||||
getLogger().warning(" / | \\");
|
||||
getLogger().warning(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName()));
|
||||
getLogger().warning(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps"));
|
||||
getLogger().warning(" / o \\");
|
||||
getLogger().warning("/_____________\\");
|
||||
geyserLogger.error(" / \\");
|
||||
geyserLogger.error(" / \\");
|
||||
geyserLogger.error(" / | \\");
|
||||
geyserLogger.error(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName()));
|
||||
geyserLogger.error(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps"));
|
||||
geyserLogger.error(" / o \\");
|
||||
geyserLogger.error("/_____________\\");
|
||||
}
|
||||
|
||||
if (!this.loadConfig()) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode());
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this);
|
||||
this.geyserInjector = new GeyserBungeeInjector(this);
|
||||
@ -293,7 +292,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
"config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
@ -6,6 +6,13 @@ loom {
|
||||
mixin.defaultRefmapName.set("geyser-refmap.json")
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
// We don't need these
|
||||
tasks.named("remapModrinthJar").configure {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.core)
|
||||
compileOnly(libs.mixin)
|
||||
|
@ -63,6 +63,7 @@ tasks {
|
||||
|
||||
modrinth {
|
||||
loaders.add("fabric")
|
||||
uploadFile.set(tasks.getByPath("remapModrinthJar"))
|
||||
dependencies {
|
||||
required.project("fabric-api")
|
||||
}
|
||||
|
@ -23,8 +23,8 @@
|
||||
"geyser.mixins.json"
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.15.10",
|
||||
"fabricloader": ">=0.15.11",
|
||||
"fabric": "*",
|
||||
"minecraft": ">=1.20.5"
|
||||
"minecraft": ">=1.21"
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,9 @@ dependencies {
|
||||
|
||||
// Let's shade in our own api
|
||||
shadow(projects.api) { isTransitive = false }
|
||||
shadow(projects.common) { isTransitive = false }
|
||||
|
||||
// cannot be shaded, since neoforge will complain if floodgate-neoforge tries to provide this
|
||||
include(projects.common)
|
||||
|
||||
// Include all transitive deps of core via JiJ
|
||||
includeTransitive(projects.core)
|
||||
@ -53,4 +55,5 @@ tasks {
|
||||
|
||||
modrinth {
|
||||
loaders.add("neoforge")
|
||||
uploadFile.set(tasks.getByPath("remapModrinthJar"))
|
||||
}
|
@ -27,6 +27,7 @@ package org.geysermc.geyser.platform.neoforge;
|
||||
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.neoforged.fml.ModContainer;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.fml.loading.FMLLoader;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
@ -43,8 +44,8 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
|
||||
|
||||
private final GeyserNeoForgePermissionHandler permissionHandler = new GeyserNeoForgePermissionHandler();
|
||||
|
||||
public GeyserNeoForgeBootstrap() {
|
||||
super(new GeyserNeoForgePlatform());
|
||||
public GeyserNeoForgeBootstrap(ModContainer container) {
|
||||
super(new GeyserNeoForgePlatform(container));
|
||||
|
||||
if (isServer()) {
|
||||
// Set as an event so we can get the proper IP and port if needed
|
||||
|
@ -26,20 +26,29 @@
|
||||
package org.geysermc.geyser.platform.neoforge;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.neoforged.fml.ModContainer;
|
||||
import net.neoforged.fml.ModList;
|
||||
import net.neoforged.fml.loading.FMLPaths;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
|
||||
import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class GeyserNeoForgePlatform implements GeyserModPlatform {
|
||||
|
||||
private final ModContainer container;
|
||||
|
||||
public GeyserNeoForgePlatform(ModContainer container) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull PlatformType platformType() {
|
||||
return PlatformType.NEOFORGE;
|
||||
@ -62,11 +71,21 @@ public class GeyserNeoForgePlatform implements GeyserModPlatform {
|
||||
|
||||
@Override
|
||||
public boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap) {
|
||||
return false; // No Floodgate mod for NeoForge yet
|
||||
if (ModList.get().isLoaded("floodgate")) {
|
||||
Path floodgateDataFolder = FMLPaths.CONFIGDIR.get().resolve("floodgate");
|
||||
bootstrap.getGeyserConfig().loadFloodgate(bootstrap, floodgateDataFolder);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InputStream resolveResource(@NonNull String resource) {
|
||||
return GeyserBootstrap.class.getClassLoader().getResourceAsStream(resource);
|
||||
try {
|
||||
Path path = container.getModInfo().getOwningFile().getFile().findResource(resource);
|
||||
return Files.newInputStream(path);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,12 +14,12 @@ config = "geyser.mixins.json"
|
||||
[[dependencies.geyser_neoforge]]
|
||||
modId="neoforge"
|
||||
type="required"
|
||||
versionRange="[20.5.0-beta,)"
|
||||
versionRange="[21.0.0-beta,)"
|
||||
ordering="NONE"
|
||||
side="BOTH"
|
||||
[[dependencies.geyser_neoforge]]
|
||||
modId="minecraft"
|
||||
type="required"
|
||||
versionRange="[1.20.5,1.21)"
|
||||
versionRange="[1.21,)"
|
||||
ordering="NONE"
|
||||
side="BOTH"
|
@ -34,7 +34,6 @@ import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
@ -80,7 +79,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
|
||||
private GeyserCommandManager geyserCommandManager;
|
||||
private GeyserModConfiguration geyserConfig;
|
||||
private GeyserModInjector geyserInjector;
|
||||
private GeyserModLogger geyserLogger;
|
||||
private final GeyserModLogger geyserLogger = new GeyserModLogger();
|
||||
private IGeyserPingPassthrough geyserPingPassthrough;
|
||||
private WorldManager geyserWorldManager;
|
||||
|
||||
@ -92,7 +91,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger = new GeyserModLogger(geyserConfig.isDebugMode());
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
this.geyser = GeyserImpl.load(this.platform.platformType(), this);
|
||||
|
||||
@ -288,7 +287,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserModConfiguration.class);
|
||||
return true;
|
||||
} catch (IOException ex) {
|
||||
LogManager.getLogger("geyser").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
@ -37,10 +37,6 @@ public class GeyserModLogger implements GeyserLogger {
|
||||
|
||||
private boolean debug;
|
||||
|
||||
public GeyserModLogger(boolean isDebug) {
|
||||
debug = isDebug;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String message) {
|
||||
logger.fatal(message);
|
||||
|
@ -25,55 +25,45 @@
|
||||
|
||||
package org.geysermc.geyser.platform.mod.world;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityInfo;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.Filterable;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.WritableBookContent;
|
||||
import net.minecraft.world.item.component.WrittenBookContent;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BannerBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BannerPatternLayers;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.LecternBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.DecoratedPotBlockEntity;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.erosion.util.LecternUtils;
|
||||
import org.geysermc.geyser.level.GeyserWorldManager;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.BlockEntityUtils;
|
||||
import org.geysermc.geyser.util.MinecraftKey;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class GeyserModWorldManager extends GeyserWorldManager {
|
||||
|
||||
private static final GsonComponentSerializer GSON_SERIALIZER = GsonComponentSerializer.gson();
|
||||
private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.legacySection();
|
||||
private final MinecraftServer server;
|
||||
|
||||
public GeyserModWorldManager(MinecraftServer server) {
|
||||
@ -121,94 +111,6 @@ public class GeyserModWorldManager extends GeyserWorldManager {
|
||||
return SharedConstants.getCurrentVersion().getProtocolVersion() == GameProtocol.getJavaProtocolVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExpectLecternHandled(GeyserSession session) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLecternData(GeyserSession session, int x, int z, List<BlockEntityInfo> blockEntityInfos) {
|
||||
server.execute(() -> {
|
||||
ServerPlayer player = getPlayer(session);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection resource - level() is just a getter
|
||||
LevelChunk chunk = player.level().getChunk(x, z);
|
||||
final int chunkBlockX = x << 4;
|
||||
final int chunkBlockZ = z << 4;
|
||||
//noinspection ForLoopReplaceableByForEach - avoid constructing iterator
|
||||
for (int i = 0; i < blockEntityInfos.size(); i++) {
|
||||
BlockEntityInfo blockEntityInfo = blockEntityInfos.get(i);
|
||||
BlockEntity blockEntity = chunk.getBlockEntity(new BlockPos(chunkBlockX + blockEntityInfo.getX(),
|
||||
blockEntityInfo.getY(), chunkBlockZ + blockEntityInfo.getZ()));
|
||||
sendLecternData(session, blockEntity, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLecternData(GeyserSession session, int x, int y, int z) {
|
||||
server.execute(() -> {
|
||||
ServerPlayer player = getPlayer(session);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
//noinspection resource - level() is just a getter
|
||||
BlockEntity blockEntity = player.level().getBlockEntity(new BlockPos(x, y, z));
|
||||
sendLecternData(session, blockEntity, false);
|
||||
});
|
||||
}
|
||||
|
||||
private void sendLecternData(GeyserSession session, BlockEntity blockEntity, boolean isChunkLoad) {
|
||||
if (!(blockEntity instanceof LecternBlockEntity lectern)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int x = blockEntity.getBlockPos().getX();
|
||||
int y = blockEntity.getBlockPos().getY();
|
||||
int z = blockEntity.getBlockPos().getZ();
|
||||
|
||||
if (!lectern.hasBook()) {
|
||||
if (!isChunkLoad) {
|
||||
BlockEntityUtils.updateBlockEntity(session, LecternUtils.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack book = lectern.getBook();
|
||||
int pageCount = getPageCount(book);
|
||||
boolean hasBookPages = pageCount > 0;
|
||||
NbtMapBuilder lecternTag = LecternUtils.getBaseLecternTag(x, y, z, hasBookPages ? pageCount : 1);
|
||||
lecternTag.putInt("page", lectern.getPage() / 2);
|
||||
NbtMapBuilder bookTag = NbtMap.builder()
|
||||
.putByte("Count", (byte) book.getCount())
|
||||
.putShort("Damage", (short) 0)
|
||||
.putString("Name", "minecraft:writable_book");
|
||||
List<NbtMap> pages = new ArrayList<>(hasBookPages ? pageCount : 1);
|
||||
if (hasBookPages) {
|
||||
List<String> bookPages = getPages(book);
|
||||
for (String page : bookPages) {
|
||||
NbtMapBuilder pageBuilder = NbtMap.builder()
|
||||
.putString("photoname", "")
|
||||
.putString("text", page);
|
||||
pages.add(pageBuilder.build());
|
||||
}
|
||||
} else {
|
||||
// Empty page
|
||||
NbtMapBuilder pageBuilder = NbtMap.builder()
|
||||
.putString("photoname", "")
|
||||
.putString("text", "");
|
||||
pages.add(pageBuilder.build());
|
||||
}
|
||||
|
||||
bookTag.putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, pages).build());
|
||||
lecternTag.putCompound("book", bookTag.build());
|
||||
NbtMap blockEntityTag = lecternTag.build();
|
||||
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(GeyserSession session, String permission) {
|
||||
ServerPlayer player = getPlayer(session);
|
||||
@ -263,43 +165,31 @@ public class GeyserModWorldManager extends GeyserWorldManager {
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDecoratedPotData(GeyserSession session, Vector3i pos, Consumer<List<String>> apply) {
|
||||
server.execute(() -> {
|
||||
ServerPlayer player = getPlayer(session);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BlockPos blockPos = new BlockPos(pos.getX(), pos.getY(), pos.getZ());
|
||||
// Don't create a new block entity if invalid
|
||||
//noinspection resource - level() is just a getter
|
||||
BlockEntity blockEntity = player.level().getChunkAt(blockPos).getBlockEntity(blockPos);
|
||||
if (blockEntity instanceof DecoratedPotBlockEntity pot) {
|
||||
List<String> sherds = pot.getDecorations().ordered()
|
||||
.stream().map(item -> BuiltInRegistries.ITEM.getKey(item).toString())
|
||||
.toList();
|
||||
apply.accept(sherds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ServerPlayer getPlayer(GeyserSession session) {
|
||||
return server.getPlayerList().getPlayer(session.getPlayerEntity().getUuid());
|
||||
}
|
||||
|
||||
private static int getPageCount(ItemStack itemStack) {
|
||||
WrittenBookContent writtenBookContent = itemStack.get(DataComponents.WRITTEN_BOOK_CONTENT);
|
||||
if (writtenBookContent != null) {
|
||||
return writtenBookContent.pages().size();
|
||||
} else {
|
||||
WritableBookContent writableBookContent = itemStack.get(DataComponents.WRITABLE_BOOK_CONTENT);
|
||||
return writableBookContent != null ? writableBookContent.pages().size() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> getPages(ItemStack itemStack) {
|
||||
WrittenBookContent writtenBookContent = itemStack.get(DataComponents.WRITTEN_BOOK_CONTENT);
|
||||
if (writtenBookContent != null) {
|
||||
return writtenBookContent.pages().stream()
|
||||
.map(Filterable::raw)
|
||||
.map(GeyserModWorldManager::fromComponent)
|
||||
.toList();
|
||||
} else {
|
||||
WritableBookContent writableBookContent = itemStack.get(DataComponents.WRITABLE_BOOK_CONTENT);
|
||||
if (writableBookContent == null) {
|
||||
return List.of();
|
||||
}
|
||||
return writableBookContent.pages().stream()
|
||||
.map(Filterable::raw)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
private static String fromComponent(Component component) {
|
||||
String json = Component.Serializer.toJson(component, RegistryAccess.EMPTY);
|
||||
return LEGACY_SERIALIZER.serialize(GSON_SERIALIZER.deserializeOr(json, net.kyori.adventure.text.Component.empty()));
|
||||
}
|
||||
|
||||
private static net.kyori.adventure.text.Component toKyoriComponent(Component component) {
|
||||
String json = Component.Serializer.toJson(component, RegistryAccess.EMPTY);
|
||||
return GSON_SERIALIZER.deserializeOr(json, net.kyori.adventure.text.Component.empty());
|
||||
@ -309,7 +199,7 @@ public class GeyserModWorldManager extends GeyserWorldManager {
|
||||
return patternLayers.layers().stream()
|
||||
.map(layer -> {
|
||||
BannerPatternLayer.BannerPattern pattern = new BannerPatternLayer.BannerPattern(
|
||||
layer.pattern().value().assetId().toString(), layer.pattern().value().translationKey()
|
||||
MinecraftKey.key(layer.pattern().value().assetId().toString()), layer.pattern().value().translationKey()
|
||||
);
|
||||
return new BannerPatternLayer(Holder.ofCustom(pattern), layer.color().getId());
|
||||
})
|
||||
|
@ -4,6 +4,12 @@ dependencies {
|
||||
isTransitive = false
|
||||
}
|
||||
|
||||
implementation(libs.erosion.bukkit.nms) {
|
||||
attributes {
|
||||
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 21)
|
||||
}
|
||||
}
|
||||
|
||||
implementation(variantOf(libs.adapters.spigot) {
|
||||
classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations
|
||||
})
|
||||
@ -70,3 +76,8 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
exclude(dependency("com.mojang:.*"))
|
||||
}
|
||||
}
|
||||
|
||||
modrinth {
|
||||
uploadFile.set(tasks.getByPath("shadowJar"))
|
||||
loaders.addAll("spigot", "paper")
|
||||
}
|
||||
|
@ -34,8 +34,8 @@ import java.util.logging.Logger;
|
||||
public final class GeyserPaperLogger extends GeyserSpigotLogger {
|
||||
private final ComponentLogger componentLogger;
|
||||
|
||||
public GeyserPaperLogger(Plugin plugin, Logger logger, boolean debug) {
|
||||
super(logger, debug);
|
||||
public GeyserPaperLogger(Plugin plugin, Logger logger) {
|
||||
super(logger);
|
||||
componentLogger = plugin.getComponentLogger();
|
||||
}
|
||||
|
||||
|
@ -25,15 +25,15 @@
|
||||
|
||||
package org.geysermc.geyser.platform.spigot;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
public class GeyserSpigotLogger implements GeyserLogger {
|
||||
private final Logger logger;
|
||||
@Getter @Setter
|
||||
|
@ -79,14 +79,14 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
|
||||
private GeyserSpigotCommandManager geyserCommandManager;
|
||||
private GeyserSpigotConfiguration geyserConfig;
|
||||
private GeyserSpigotInjector geyserInjector;
|
||||
private GeyserSpigotLogger geyserLogger;
|
||||
private final GeyserSpigotLogger geyserLogger = GeyserPaperLogger.supported() ?
|
||||
new GeyserPaperLogger(this, getLogger()) : new GeyserSpigotLogger(getLogger());
|
||||
private IGeyserPingPassthrough geyserSpigotPingPassthrough;
|
||||
private GeyserSpigotWorldManager geyserWorldManager;
|
||||
|
||||
@ -114,12 +114,12 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
// We depend on this as a fallback in certain scenarios
|
||||
BlockData.class.getMethod("getAsString");
|
||||
} catch (ClassNotFoundException | NoSuchMethodException e) {
|
||||
getLogger().severe("*********************************************");
|
||||
getLogger().severe("");
|
||||
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.header"));
|
||||
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2"));
|
||||
getLogger().severe("");
|
||||
getLogger().severe("*********************************************");
|
||||
geyserLogger.error("*********************************************");
|
||||
geyserLogger.error("");
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.header"));
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2"));
|
||||
geyserLogger.error("");
|
||||
geyserLogger.error("*********************************************");
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
@ -128,12 +128,12 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
Class.forName("net.md_5.bungee.chat.ComponentSerializer");
|
||||
} catch (ClassNotFoundException e) {
|
||||
if (!PaperAdventure.canSendMessageUsingComponent()) { // Prepare for Paper eventually removing Bungee chat
|
||||
getLogger().severe("*********************************************");
|
||||
getLogger().severe("");
|
||||
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.header", getServer().getName()));
|
||||
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper"));
|
||||
getLogger().severe("");
|
||||
getLogger().severe("*********************************************");
|
||||
geyserLogger.error("*********************************************");
|
||||
geyserLogger.error("");
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.header", getServer().getName()));
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper"));
|
||||
geyserLogger.error("");
|
||||
geyserLogger.error("*********************************************");
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
@ -142,11 +142,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
try {
|
||||
Class.forName("io.netty.util.internal.ObjectPool$ObjectCreator");
|
||||
} catch (ClassNotFoundException e) {
|
||||
getLogger().severe("*********************************************");
|
||||
getLogger().severe("");
|
||||
getLogger().severe("This version of Spigot is using an outdated version of netty. Please use Paper instead!");
|
||||
getLogger().severe("");
|
||||
getLogger().severe("*********************************************");
|
||||
geyserLogger.error("*********************************************");
|
||||
geyserLogger.error("");
|
||||
geyserLogger.error("This version of Spigot is using an outdated version of netty. Please use Paper instead!");
|
||||
geyserLogger.error("");
|
||||
geyserLogger.error("*********************************************");
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
@ -154,8 +154,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode())
|
||||
: new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode());
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
|
||||
// Turn "(MC: 1.16.4)" into 1.16.4.
|
||||
@ -252,6 +251,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
SpigotAdapters.registerWorldAdapter(nmsVersion);
|
||||
geyserLogger.debug("Using spigot NMS adapter for nms version: " + nmsVersion);
|
||||
} catch (Exception e) { // Likely running on Paper 1.20.5+
|
||||
geyserLogger.debug("Unable to find spigot world manager: " + e.getMessage());
|
||||
//noinspection deprecation
|
||||
int protocolVersion = Bukkit.getUnsafe().getProtocolVersion();
|
||||
PaperAdapters.registerClosestWorldAdapter(protocolVersion);
|
||||
@ -266,7 +266,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this, isPaper);
|
||||
}
|
||||
geyserLogger.debug("Using world manager of type: " + this.geyserWorldManager.getClass().getSimpleName());
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
if (geyserConfig.isDebugMode()) {
|
||||
geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)");
|
||||
e.printStackTrace();
|
||||
@ -486,7 +486,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return false;
|
||||
|
@ -25,10 +25,8 @@
|
||||
|
||||
package org.geysermc.geyser.platform.spigot.world;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
@ -40,13 +38,17 @@ import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockPistonEvent;
|
||||
import org.bukkit.event.block.BlockPistonExtendEvent;
|
||||
import org.bukkit.event.block.BlockPistonRetractEvent;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.block.property.Properties;
|
||||
import org.geysermc.geyser.level.block.type.BlockState;
|
||||
import org.geysermc.geyser.level.physics.Direction;
|
||||
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.PistonCache;
|
||||
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -85,7 +87,7 @@ public class GeyserPistonListener implements Listener {
|
||||
PistonValueType type = isExtend ? PistonValueType.PUSHING : PistonValueType.PULLING;
|
||||
boolean sticky = event.isSticky();
|
||||
|
||||
Object2IntMap<Vector3i> attachedBlocks = new Object2IntArrayMap<>();
|
||||
Object2ObjectMap<Vector3i, BlockState> attachedBlocks = new Object2ObjectArrayMap<>();
|
||||
boolean blocksFilled = false;
|
||||
|
||||
for (Map.Entry<UUID, GeyserSession> entry : geyser.getSessionManager().getSessions().entrySet()) {
|
||||
@ -108,10 +110,10 @@ public class GeyserPistonListener implements Listener {
|
||||
List<Block> blocks = isExtend ? ((BlockPistonExtendEvent) event).getBlocks() : ((BlockPistonRetractEvent) event).getBlocks();
|
||||
for (Block block : blocks) {
|
||||
Location attachedLocation = block.getLocation();
|
||||
int blockId = worldManager.getBlockNetworkId(block);
|
||||
BlockState state = BlockState.of(worldManager.getBlockNetworkId(block));
|
||||
// Ignore blocks that will be destroyed
|
||||
if (BlockStateValues.canPistonMoveBlock(blockId, isExtend)) {
|
||||
attachedBlocks.put(getVector(attachedLocation), blockId);
|
||||
if (BlockStateValues.canPistonMoveBlock(state, isExtend)) {
|
||||
attachedBlocks.put(getVector(attachedLocation), state);
|
||||
}
|
||||
}
|
||||
blocksFilled = true;
|
||||
@ -119,7 +121,7 @@ public class GeyserPistonListener implements Listener {
|
||||
|
||||
int pistonBlockId = worldManager.getBlockNetworkId(event.getBlock());
|
||||
// event.getDirection() is unreliable
|
||||
Direction orientation = BlockStateValues.getPistonOrientation(pistonBlockId);
|
||||
Direction orientation = BlockState.of(pistonBlockId).getValue(Properties.FACING);
|
||||
|
||||
session.executeInEventLoop(() -> {
|
||||
PistonCache pistonCache = session.getPistonCache();
|
||||
|
@ -33,7 +33,7 @@ import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.block.type.Block;
|
||||
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
@ -59,11 +59,11 @@ public class GeyserSpigotBlockPlaceListener implements Listener {
|
||||
event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())));
|
||||
} else {
|
||||
String javaBlockId = event.getBlockPlaced().getBlockData().getAsString();
|
||||
placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().getOrDefault(javaBlockId, BlockStateValues.JAVA_AIR_ID)));
|
||||
placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().getOrDefault(javaBlockId, Block.JAVA_AIR_ID)));
|
||||
}
|
||||
placeBlockSoundPacket.setIdentifier(":");
|
||||
session.sendUpstreamPacket(placeBlockSoundPacket);
|
||||
session.setLastBlockPlacePosition(null);
|
||||
session.setLastBlockPlacedId(null);
|
||||
session.setLastBlockPlaced(null);
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import org.geysermc.geyser.adapters.WorldAdapter;
|
||||
import org.geysermc.geyser.adapters.paper.PaperAdapters;
|
||||
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.block.type.Block;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
|
||||
@ -52,7 +53,7 @@ public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
|
||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
||||
Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername());
|
||||
if (player == null) {
|
||||
return BlockStateValues.JAVA_AIR_ID;
|
||||
return Block.JAVA_AIR_ID;
|
||||
}
|
||||
return adapter.getBlockAt(player.getWorld(), x, y, z);
|
||||
}
|
||||
|
@ -25,55 +25,52 @@
|
||||
|
||||
package org.geysermc.geyser.platform.spigot.world.manager;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityInfo;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.DecoratedPot;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.geysermc.erosion.bukkit.BukkitLecterns;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.geysermc.erosion.bukkit.BukkitUtils;
|
||||
import org.geysermc.erosion.bukkit.PickBlockUtils;
|
||||
import org.geysermc.erosion.bukkit.SchedulerUtils;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.level.GameRule;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.BlockEntityUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* The base world manager to use when there is no supported NMS revision
|
||||
*/
|
||||
public class GeyserSpigotWorldManager extends WorldManager {
|
||||
private final Plugin plugin;
|
||||
private final BukkitLecterns lecterns;
|
||||
|
||||
public GeyserSpigotWorldManager(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.lecterns = new BukkitLecterns(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
|
||||
return BlockStateValues.JAVA_AIR_ID;
|
||||
return org.geysermc.geyser.level.block.type.Block.JAVA_AIR_ID;
|
||||
}
|
||||
World world = bukkitPlayer.getWorld();
|
||||
if (!world.isChunkLoaded(x >> 4, z >> 4)) {
|
||||
// If the chunk isn't loaded, how could we even be here?
|
||||
return BlockStateValues.JAVA_AIR_ID;
|
||||
return org.geysermc.geyser.level.block.type.Block.JAVA_AIR_ID;
|
||||
}
|
||||
|
||||
return getBlockNetworkId(world.getBlockAt(x, y, z));
|
||||
@ -84,9 +81,9 @@ public class GeyserSpigotWorldManager extends WorldManager {
|
||||
// Terrible behavior, but this is basically what's always been happening behind the scenes anyway.
|
||||
CompletableFuture<String> blockData = new CompletableFuture<>();
|
||||
Bukkit.getRegionScheduler().execute(this.plugin, block.getLocation(), () -> blockData.complete(block.getBlockData().getAsString()));
|
||||
return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(blockData.join(), BlockStateValues.JAVA_AIR_ID);
|
||||
return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(blockData.join(), org.geysermc.geyser.level.block.type.Block.JAVA_AIR_ID);
|
||||
}
|
||||
return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(block.getBlockData().getAsString(), BlockStateValues.JAVA_AIR_ID);
|
||||
return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(block.getBlockData().getAsString(), org.geysermc.geyser.level.block.type.Block.JAVA_AIR_ID); // TODO could just make this a BlockState lookup?
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -94,69 +91,6 @@ public class GeyserSpigotWorldManager extends WorldManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLecternData(GeyserSession session, int x, int y, int z) {
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
|
||||
// Run as a task to prevent async issues
|
||||
SchedulerUtils.runTask(this.plugin, () -> sendLecternData(session, block, false), block);
|
||||
}
|
||||
|
||||
public void sendLecternData(GeyserSession session, int x, int z, List<BlockEntityInfo> blockEntityInfos) {
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
|
||||
return;
|
||||
}
|
||||
if (SchedulerUtils.FOLIA) {
|
||||
Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
|
||||
if (chunk == null) {
|
||||
return;
|
||||
}
|
||||
Bukkit.getRegionScheduler().execute(this.plugin, bukkitPlayer.getWorld(), x, z, () ->
|
||||
sendLecternData(session, chunk, blockEntityInfos));
|
||||
} else {
|
||||
Bukkit.getScheduler().runTask(this.plugin, () -> {
|
||||
Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
|
||||
if (chunk == null) {
|
||||
return;
|
||||
}
|
||||
sendLecternData(session, chunk, blockEntityInfos);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Chunk getChunk(World world, int x, int z) {
|
||||
if (!world.isChunkLoaded(x, z)) {
|
||||
return null;
|
||||
}
|
||||
return world.getChunkAt(x, z);
|
||||
}
|
||||
|
||||
private void sendLecternData(GeyserSession session, Chunk chunk, List<BlockEntityInfo> blockEntityInfos) {
|
||||
//noinspection ForLoopReplaceableByForEach - avoid constructing Iterator
|
||||
for (int i = 0; i < blockEntityInfos.size(); i++) {
|
||||
BlockEntityInfo info = blockEntityInfos.get(i);
|
||||
Block block = chunk.getBlock(info.getX(), info.getY(), info.getZ());
|
||||
sendLecternData(session, block, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendLecternData(GeyserSession session, Block block, boolean isChunkLoad) {
|
||||
NbtMap blockEntityTag = this.lecterns.getLecternData(block, isChunkLoad);
|
||||
if (blockEntityTag != null) {
|
||||
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, BukkitUtils.getVector(block.getLocation()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExpectLecternHandled(GeyserSession session) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
|
||||
org.bukkit.GameRule<?> bukkitGameRule = org.bukkit.GameRule.getByName(gameRule.getJavaID());
|
||||
if (bukkitGameRule == null) {
|
||||
@ -205,17 +139,31 @@ public class GeyserSpigotWorldManager extends WorldManager {
|
||||
|
||||
@Override
|
||||
public @NonNull CompletableFuture<@Nullable DataComponents> getPickItemComponents(GeyserSession session, int x, int y, int z, boolean addNbtData) {
|
||||
CompletableFuture<@Nullable DataComponents> future = new CompletableFuture<>();
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {
|
||||
future.complete(null);
|
||||
return future;
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
CompletableFuture<Int2ObjectMap<byte[]>> future = new CompletableFuture<>();
|
||||
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
|
||||
// Paper 1.19.3 complains about async access otherwise.
|
||||
// java.lang.IllegalStateException: Tile is null, asynchronous access?
|
||||
SchedulerUtils.runTask(this.plugin, () -> future.complete(/*PickBlockUtils.pickBlock(block)*/ null), block); // TODO fix erosion once clear how to handle this
|
||||
return future;
|
||||
SchedulerUtils.runTask(this.plugin, () -> future.complete(PickBlockUtils.pickBlock(block)), block);
|
||||
return future.thenApply(RAW_TRANSFORMER);
|
||||
}
|
||||
|
||||
public void getDecoratedPotData(GeyserSession session, Vector3i pos, Consumer<List<String>> apply) {
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {
|
||||
return;
|
||||
}
|
||||
Block block = bukkitPlayer.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
|
||||
SchedulerUtils.runTask(this.plugin, () -> {
|
||||
var state = BukkitUtils.getBlockState(block);
|
||||
if (!(state instanceof DecoratedPot pot)) {
|
||||
return;
|
||||
}
|
||||
apply.accept(pot.getShards().stream().map(material -> material.getKey().toString()).toList());
|
||||
}, block);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,7 +71,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
|
||||
private GeyserCommandManager geyserCommandManager;
|
||||
private GeyserStandaloneConfiguration geyserConfig;
|
||||
private GeyserStandaloneLogger geyserLogger;
|
||||
private final GeyserStandaloneLogger geyserLogger = new GeyserStandaloneLogger();
|
||||
private IGeyserPingPassthrough geyserPingPassthrough;
|
||||
private GeyserStandaloneGUI gui;
|
||||
@Getter
|
||||
@ -181,8 +181,6 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
this.geyserLogger = new GeyserStandaloneLogger();
|
||||
|
||||
if (useGui && gui == null) {
|
||||
gui = new GeyserStandaloneGUI(geyserLogger);
|
||||
gui.redirectSystemStreams();
|
||||
|
@ -70,3 +70,8 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
exclude(dependency("net.kyori:adventure-nbt:.*"))
|
||||
}
|
||||
}
|
||||
|
||||
modrinth {
|
||||
uploadFile.set(tasks.getByPath("shadowJar"))
|
||||
loaders.addAll("velocity")
|
||||
}
|
@ -25,13 +25,13 @@
|
||||
|
||||
package org.geysermc.geyser.platform.velocity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
public class GeyserVelocityLogger implements GeyserLogger {
|
||||
private final Logger logger;
|
||||
@Getter @Setter
|
||||
|
@ -64,44 +64,44 @@ import java.util.UUID;
|
||||
|
||||
@Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC")
|
||||
public class GeyserVelocityPlugin implements GeyserBootstrap {
|
||||
@Inject
|
||||
private Logger logger;
|
||||
|
||||
@Inject
|
||||
private ProxyServer proxyServer;
|
||||
|
||||
@Inject
|
||||
private CommandManager commandManager;
|
||||
|
||||
private final ProxyServer proxyServer;
|
||||
private final CommandManager commandManager;
|
||||
private final GeyserVelocityLogger geyserLogger;
|
||||
private GeyserCommandManager geyserCommandManager;
|
||||
private GeyserVelocityConfiguration geyserConfig;
|
||||
private GeyserVelocityInjector geyserInjector;
|
||||
private GeyserVelocityLogger geyserLogger;
|
||||
private IGeyserPingPassthrough geyserPingPassthrough;
|
||||
|
||||
private GeyserImpl geyser;
|
||||
|
||||
@Getter
|
||||
private final Path configFolder = Paths.get("plugins/" + GeyserImpl.NAME + "-Velocity/");
|
||||
|
||||
@Inject
|
||||
public GeyserVelocityPlugin(ProxyServer server, Logger logger, CommandManager manager) {
|
||||
this.geyserLogger = new GeyserVelocityLogger(logger);
|
||||
this.proxyServer = server;
|
||||
this.commandManager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeyserInitialize() {
|
||||
GeyserLocale.init(this);
|
||||
|
||||
if (!ProtocolVersion.isSupported(GameProtocol.getJavaProtocolVersion())) {
|
||||
logger.error(" / \\");
|
||||
logger.error(" / \\");
|
||||
logger.error(" / | \\");
|
||||
logger.error(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", proxyServer.getVersion().getName()));
|
||||
logger.error(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps"));
|
||||
logger.error(" / o \\");
|
||||
logger.error("/_____________\\");
|
||||
geyserLogger.error(" / \\");
|
||||
geyserLogger.error(" / \\");
|
||||
geyserLogger.error(" / | \\");
|
||||
geyserLogger.error(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", proxyServer.getVersion().getName()));
|
||||
geyserLogger.error(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps"));
|
||||
geyserLogger.error(" / o \\");
|
||||
geyserLogger.error("/_____________\\");
|
||||
}
|
||||
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger = new GeyserVelocityLogger(logger, geyserConfig.isDebugMode());
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
|
||||
this.geyser = GeyserImpl.load(PlatformType.VELOCITY, this);
|
||||
@ -249,7 +249,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
||||
"config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
@ -27,14 +27,23 @@ package org.geysermc.geyser.platform.viaproxy;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import net.raphimc.vialegacy.api.LegacyProtocolVersion;
|
||||
import net.raphimc.viaproxy.ViaProxy;
|
||||
import net.raphimc.viaproxy.protocoltranslator.viaproxy.ViaProxyConfig;
|
||||
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
|
||||
public class GeyserViaProxyConfiguration extends GeyserJacksonConfiguration {
|
||||
|
||||
private RemoteConfiguration remote = new RemoteConfiguration() {
|
||||
@Override
|
||||
public boolean isForwardHost() {
|
||||
return super.isForwardHost() || !ViaProxy.getConfig().getWildcardDomainHandling().equals(ViaProxyConfig.WildcardDomainHandling.NONE);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public Path getFloodgateKeyPath() {
|
||||
return new File(GeyserViaProxyPlugin.ROOT_FOLDER, this.getFloodgateKeyFile()).toPath();
|
||||
@ -50,4 +59,9 @@ public class GeyserViaProxyConfiguration extends GeyserJacksonConfiguration {
|
||||
return interval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteConfiguration getRemote() {
|
||||
return this.remote;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.api.event.EventRegistrar;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
@ -44,6 +45,7 @@ import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
|
||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.geyser.platform.viaproxy.listener.GeyserServerTransferListener;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
@ -57,7 +59,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.UUID;
|
||||
|
||||
public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootstrap {
|
||||
public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootstrap, EventRegistrar {
|
||||
|
||||
public static final File ROOT_FOLDER = new File(PluginManager.PLUGINS_DIR, "Geyser");
|
||||
|
||||
@ -120,6 +122,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
|
||||
}
|
||||
|
||||
this.geyser = GeyserImpl.load(PlatformType.VIAPROXY, this);
|
||||
this.geyser.eventBus().register(this, new GeyserServerTransferListener());
|
||||
LoopbackUtil.checkAndApplyLoopback(this.logger);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.platform.viaproxy.listener;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.net.HostAndPort;
|
||||
import org.geysermc.event.PostOrder;
|
||||
import org.geysermc.event.subscribe.Subscribe;
|
||||
import org.geysermc.geyser.api.event.bedrock.SessionLoginEvent;
|
||||
import org.geysermc.geyser.api.event.java.ServerTransferEvent;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class GeyserServerTransferListener {
|
||||
|
||||
private final Cache<String, Map<String, byte[]>> cookieStorages = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build();
|
||||
|
||||
@Subscribe(postOrder = PostOrder.FIRST)
|
||||
private void onServerTransfer(final ServerTransferEvent event) {
|
||||
this.cookieStorages.put(event.connection().xuid(), event.cookies());
|
||||
final GeyserSession geyserSession = (GeyserSession) event.connection();
|
||||
final HostAndPort hostAndPort = HostAndPort.fromString(geyserSession.getClientData().getServerAddress()).withDefaultPort(19132);
|
||||
event.bedrockHost(hostAndPort.getHost());
|
||||
event.bedrockPort(hostAndPort.getPort());
|
||||
}
|
||||
|
||||
@Subscribe(postOrder = PostOrder.FIRST)
|
||||
private void onSessionLogin(final SessionLoginEvent event) {
|
||||
final Map<String, byte[]> cookies = this.cookieStorages.asMap().remove(event.connection().xuid());
|
||||
if (cookies != null) {
|
||||
event.cookies(cookies);
|
||||
event.transferring(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -12,6 +12,9 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// this is OK as long as the same version catalog is used in the main build and build-logic
|
||||
// see https://github.com/gradle/gradle/issues/15383#issuecomment-779893192
|
||||
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
|
||||
implementation(libs.indra)
|
||||
implementation(libs.shadow)
|
||||
implementation(libs.architectury.plugin)
|
||||
|
6
build-logic/src/main/kotlin/LibsAccessor.kt
Normale Datei
6
build-logic/src/main/kotlin/LibsAccessor.kt
Normale Datei
@ -0,0 +1,6 @@
|
||||
import org.gradle.accessors.dm.LibrariesForLibs
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.getByType
|
||||
|
||||
val Project.libs: LibrariesForLibs
|
||||
get() = rootProject.extensions.getByType()
|
@ -8,7 +8,6 @@ plugins {
|
||||
id("geyser.publish-conventions")
|
||||
id("architectury-plugin")
|
||||
id("dev.architectury.loom")
|
||||
id("com.modrinth.minotaur")
|
||||
}
|
||||
|
||||
// These are provided by Minecraft/modded platforms already, no need to include them
|
||||
@ -39,7 +38,7 @@ provided("io.netty", "netty-resolver-dns-native-macos")
|
||||
provided("org.ow2.asm", "asm")
|
||||
|
||||
architectury {
|
||||
minecraft = "1.20.5"
|
||||
minecraft = libs.minecraft.get().version as String
|
||||
}
|
||||
|
||||
loom {
|
||||
@ -83,7 +82,7 @@ tasks {
|
||||
register("remapModrinthJar", RemapJarTask::class) {
|
||||
dependsOn(shadowJar)
|
||||
inputFile.set(shadowJar.get().archiveFile)
|
||||
archiveVersion.set(project.version.toString() + "+build." + System.getenv("GITHUB_RUN_NUMBER"))
|
||||
archiveVersion.set(project.version.toString() + "+build." + System.getenv("BUILD_NUMBER"))
|
||||
archiveClassifier.set("")
|
||||
}
|
||||
}
|
||||
@ -93,7 +92,7 @@ afterEvaluate {
|
||||
|
||||
// These are shaded, no need to JiJ them
|
||||
configurations["shadow"].dependencies.forEach {shadowed ->
|
||||
println("Not including shadowed dependency: ${shadowed.group}:${shadowed.name}")
|
||||
//println("Not including shadowed dependency: ${shadowed.group}:${shadowed.name}")
|
||||
providedDependencies.add("${shadowed.group}:${shadowed.name}")
|
||||
}
|
||||
|
||||
@ -101,39 +100,24 @@ afterEvaluate {
|
||||
configurations["includeTransitive"].resolvedConfiguration.resolvedArtifacts.forEach { dep ->
|
||||
if (!providedDependencies.contains("${dep.moduleVersion.id.group}:${dep.moduleVersion.id.name}")
|
||||
and !providedDependencies.contains("${dep.moduleVersion.id.group}:.*")) {
|
||||
println("Including dependency via JiJ: ${dep.id}")
|
||||
//println("Including dependency via JiJ: ${dep.id}")
|
||||
dependencies.add("include", dep.moduleVersion.id.toString())
|
||||
} else {
|
||||
println("Not including ${dep.id} for ${project.name}!")
|
||||
//println("Not including ${dep.id} for ${project.name}!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft("com.mojang:minecraft:1.20.5")
|
||||
minecraft(libs.minecraft)
|
||||
mappings(loom.officialMojangMappings())
|
||||
}
|
||||
|
||||
repositories {
|
||||
// mavenLocal()
|
||||
maven("https://repo.opencollab.dev/maven-releases/")
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.opencollab.dev/main")
|
||||
maven("https://jitpack.io")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots/")
|
||||
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||
maven("https://maven.neoforged.net/releases")
|
||||
}
|
||||
|
||||
modrinth {
|
||||
token.set(System.getenv("MODRINTH_TOKEN")) // Even though this is the default value, apparently this prevents GitHub Actions caching the token?
|
||||
projectId.set("wKkoqHrH")
|
||||
versionNumber.set(project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER"))
|
||||
versionType.set("beta")
|
||||
changelog.set("A changelog can be found at https://github.com/GeyserMC/Geyser/commits")
|
||||
|
||||
syncBodyFrom.set(rootProject.file("README.md").readText())
|
||||
|
||||
uploadFile.set(tasks.getByPath("remapModrinthJar"))
|
||||
gameVersions.addAll("1.20.5", "1.20.6")
|
||||
failSilently.set(true)
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
plugins {
|
||||
id("com.modrinth.minotaur")
|
||||
}
|
||||
|
||||
// Ensure that the readme is synched
|
||||
tasks.modrinth.get().dependsOn(tasks.modrinthSyncBody)
|
||||
|
||||
modrinth {
|
||||
token.set(System.getenv("MODRINTH_TOKEN") ?: "") // Even though this is the default value, apparently this prevents GitHub Actions caching the token?
|
||||
projectId.set("geyser")
|
||||
versionNumber.set(project.version as String + "-" + System.getenv("BUILD_NUMBER"))
|
||||
versionType.set("beta")
|
||||
changelog.set(System.getenv("CHANGELOG") ?: "")
|
||||
gameVersions.add(libs.minecraft.get().version as String)
|
||||
failSilently.set(true)
|
||||
|
||||
syncBodyFrom.set(rootProject.file("README.md").readText())
|
||||
}
|
@ -26,6 +26,14 @@ val moddedPlatforms = setOf(
|
||||
projects.mod
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val modrinthPlatforms = setOf(
|
||||
projects.bungeecord,
|
||||
projects.fabric,
|
||||
projects.neoforge,
|
||||
projects.spigot,
|
||||
projects.velocity
|
||||
).map { it.dependencyProject }
|
||||
|
||||
subprojects {
|
||||
apply {
|
||||
plugin("java-library")
|
||||
@ -38,4 +46,10 @@ subprojects {
|
||||
in moddedPlatforms -> plugins.apply("geyser.modded-conventions")
|
||||
else -> plugins.apply("geyser.base-conventions")
|
||||
}
|
||||
|
||||
// Not combined with platform-conventions as that also contains
|
||||
// platforms which we cant publish to modrinth
|
||||
if (modrinthPlatforms.contains(this)) {
|
||||
plugins.apply("geyser.modrinth-uploading-conventions")
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import net.kyori.blossom.BlossomExtension
|
||||
|
||||
plugins {
|
||||
// Allow blossom to mark sources root of templates
|
||||
idea
|
||||
alias(libs.plugins.blossom)
|
||||
id("geyser.publish-conventions")
|
||||
}
|
||||
@ -24,7 +24,6 @@ dependencies {
|
||||
implementation(libs.websocket)
|
||||
|
||||
api(libs.bundles.protocol)
|
||||
implementation(libs.blockstateupdater)
|
||||
|
||||
api(libs.mcauthlib)
|
||||
api(libs.mcprotocollib) {
|
||||
@ -76,7 +75,7 @@ tasks.processResources {
|
||||
expand(
|
||||
"branch" to info.branch,
|
||||
"buildNumber" to info.buildNumber,
|
||||
"projectVersion" to project.version,
|
||||
"projectVersion" to info.version,
|
||||
"commit" to info.commit,
|
||||
"commitAbbrev" to info.commitAbbrev,
|
||||
"commitMessage" to info.commitMessage,
|
||||
@ -85,21 +84,30 @@ tasks.processResources {
|
||||
}
|
||||
}
|
||||
|
||||
configure<BlossomExtension> {
|
||||
val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java"
|
||||
sourceSets {
|
||||
main {
|
||||
blossom {
|
||||
val info = GitInfo()
|
||||
|
||||
replaceToken("\${version}", "${project.version} (${info.gitVersion})", mainFile)
|
||||
replaceToken("\${gitVersion}", info.gitVersion, mainFile)
|
||||
replaceToken("\${buildNumber}", info.buildNumber, mainFile)
|
||||
replaceToken("\${branch}", info.branch, mainFile)
|
||||
replaceToken("\${commit}", info.commit, mainFile)
|
||||
replaceToken("\${repository}", info.repository, mainFile)
|
||||
javaSources {
|
||||
property("version", "${info.version} (${info.gitVersion})")
|
||||
property("gitVersion", info.gitVersion)
|
||||
property("buildNumber", info.buildNumber.toString())
|
||||
property("branch", info.branch)
|
||||
property("commit", info.commit)
|
||||
property("repository", info.repository)
|
||||
property("devVersion", info.isDev.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.buildNumber(): Int =
|
||||
fun buildNumber(): Int =
|
||||
(System.getenv("BUILD_NUMBER"))?.let { Integer.parseInt(it) } ?: -1
|
||||
|
||||
fun isDevBuild(branch: String, repository: String): Boolean {
|
||||
return branch != "master" || repository.equals("https://github.com/GeyserMC/Geyser", ignoreCase = true).not()
|
||||
}
|
||||
|
||||
inner class GitInfo {
|
||||
val branch: String
|
||||
val commit: String
|
||||
@ -112,22 +120,25 @@ inner class GitInfo {
|
||||
val commitMessage: String
|
||||
val repository: String
|
||||
|
||||
val isDev: Boolean
|
||||
|
||||
init {
|
||||
// On Jenkins, a detached head is checked out, so indra cannot determine the branch.
|
||||
// Fortunately, this environment variable is available.
|
||||
branch = indraGit.branchName() ?: System.getenv("BRANCH_NAME") ?: "DEV"
|
||||
branch = indraGit.branchName() ?: "DEV"
|
||||
|
||||
val commit = indraGit.commit()
|
||||
this.commit = commit?.name ?: "0".repeat(40)
|
||||
commitAbbrev = commit?.name?.substring(0, 7) ?: "0".repeat(7)
|
||||
|
||||
gitVersion = "git-${branch}-${commitAbbrev}"
|
||||
version = "${project.version} ($gitVersion)"
|
||||
buildNumber = buildNumber()
|
||||
|
||||
val git = indraGit.git()
|
||||
commitMessage = git?.commit()?.message ?: ""
|
||||
repository = git?.repository?.config?.getString("remote", "origin", "url") ?: ""
|
||||
|
||||
buildNumber = buildNumber()
|
||||
isDev = isDevBuild(branch, repository)
|
||||
val projectVersion = if (isDev) project.version else project.version.toString().replace("SNAPSHOT", "b${buildNumber}")
|
||||
version = "$projectVersion ($gitVersion)"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,18 +23,20 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.level.block;
|
||||
package org.geysermc.geyser;
|
||||
|
||||
/**
|
||||
* This stores all values of double chests that are part of the Java block state.
|
||||
*
|
||||
* @param isFacingEast If true, then chest is facing east/west; if false, south/north
|
||||
* @param isDirectionPositive If true, direction is positive (east/south); if false, direction is negative (west/north)
|
||||
* @param isLeft If true, chest is the left of a pair; if false, chest is the right of a pair.
|
||||
*/
|
||||
public record DoubleChestValue(
|
||||
boolean isFacingEast,
|
||||
boolean isDirectionPositive,
|
||||
boolean isLeft) {
|
||||
// The constants are replaced before compilation
|
||||
public class BuildData {
|
||||
public static final String GIT_VERSION = "{{ gitVersion }}";
|
||||
public static final String VERSION = "{{ version }}";
|
||||
|
||||
public static final String BUILD_NUMBER = "{{ buildNumber }}";
|
||||
public static final String BRANCH = "{{ branch }}";
|
||||
public static final String COMMIT = "{{ commit }}";
|
||||
public static final String REPOSITORY = "{{ repository }}";
|
||||
private static final String DEV = "{{ devVersion }}";
|
||||
|
||||
public static boolean isDevBuild() {
|
||||
return Boolean.parseBoolean(DEV);
|
||||
}
|
||||
}
|
@ -34,7 +34,7 @@ public final class Constants {
|
||||
public static final String NEWS_OVERVIEW_URL = "https://api.geysermc.org/v2/news/";
|
||||
public static final String NEWS_PROJECT_NAME = "geyser";
|
||||
|
||||
public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/";
|
||||
public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://geysermc.org/download#floodgate";
|
||||
|
||||
public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
|
||||
public static final String UPDATE_PERMISSION = "geyser.update";
|
||||
|
@ -45,6 +45,7 @@ import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.geysermc.api.Geyser;
|
||||
import org.geysermc.cumulus.form.Form;
|
||||
import org.geysermc.cumulus.form.util.FormBuilder;
|
||||
import org.geysermc.erosion.packet.Packets;
|
||||
import org.geysermc.floodgate.crypto.AesCipher;
|
||||
import org.geysermc.floodgate.crypto.AesKeyProducer;
|
||||
import org.geysermc.floodgate.crypto.Base64Topping;
|
||||
@ -77,6 +78,7 @@ import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
|
||||
import org.geysermc.geyser.session.SessionManager;
|
||||
import org.geysermc.geyser.session.cache.RegistryCache;
|
||||
import org.geysermc.geyser.skin.FloodgateSkinUploader;
|
||||
import org.geysermc.geyser.skin.ProvidedSkins;
|
||||
import org.geysermc.geyser.skin.SkinProvider;
|
||||
@ -113,13 +115,14 @@ public class GeyserImpl implements GeyserApi {
|
||||
.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
|
||||
|
||||
public static final String NAME = "Geyser";
|
||||
public static final String GIT_VERSION = "${gitVersion}";
|
||||
public static final String VERSION = "${version}";
|
||||
public static final String GIT_VERSION = BuildData.GIT_VERSION;
|
||||
public static final String VERSION = BuildData.VERSION;
|
||||
|
||||
public static final String BUILD_NUMBER = "${buildNumber}";
|
||||
public static final String BRANCH = "${branch}";
|
||||
public static final String COMMIT = "${commit}";
|
||||
public static final String REPOSITORY = "${repository}";
|
||||
public static final String BUILD_NUMBER = BuildData.BUILD_NUMBER;
|
||||
public static final String BRANCH = BuildData.BRANCH;
|
||||
public static final String COMMIT = BuildData.COMMIT;
|
||||
public static final String REPOSITORY = BuildData.REPOSITORY;
|
||||
public static final boolean IS_DEV = BuildData.isDevBuild();
|
||||
|
||||
/**
|
||||
* Oauth client ID for Microsoft authentication
|
||||
@ -205,12 +208,20 @@ public class GeyserImpl implements GeyserApi {
|
||||
logger.info("");
|
||||
logger.info(GeyserLocale.getLocaleStringLog("geyser.core.load", NAME, VERSION));
|
||||
logger.info("");
|
||||
if (IS_DEV) {
|
||||
// TODO cloud use language string
|
||||
//logger.info(GeyserLocale.getLocaleStringLog("geyser.core.dev_build", "https://discord.gg/geysermc"));
|
||||
logger.info("You are running a development build of Geyser! Please report any bugs you find on our Discord server: %s".formatted("https://discord.gg/geysermc"));
|
||||
logger.info("");
|
||||
}
|
||||
logger.info("******************************************");
|
||||
|
||||
/* Initialize registries */
|
||||
Registries.init();
|
||||
BlockRegistries.init();
|
||||
|
||||
RegistryCache.init();
|
||||
|
||||
/* Initialize translators */
|
||||
EntityDefinitions.init();
|
||||
MessageTranslator.init();
|
||||
@ -383,7 +394,7 @@ public class GeyserImpl implements GeyserApi {
|
||||
|
||||
this.newsHandler = new NewsHandler(BRANCH, this.buildNumber());
|
||||
|
||||
//Packets.initGeyser();
|
||||
Packets.initGeyser();
|
||||
|
||||
if (Epoll.isAvailable()) {
|
||||
this.erosionUnixListener = new UnixSocketClientListener();
|
||||
@ -680,6 +691,7 @@ public class GeyserImpl implements GeyserApi {
|
||||
*
|
||||
* @return true if the version number is not 'DEV'.
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean isProductionEnvironment() {
|
||||
// First is if Blossom runs, second is if Blossom doesn't run
|
||||
//noinspection ConstantConditions,MismatchedStringCase - changes in production
|
||||
@ -766,6 +778,7 @@ public class GeyserImpl implements GeyserApi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
//noinspection DataFlowIssue
|
||||
return Integer.parseInt(BUILD_NUMBER);
|
||||
}
|
||||
|
||||
|
@ -25,8 +25,8 @@
|
||||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.geysermc.geyser.Constants;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
@ -37,8 +37,7 @@ import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.WebUtils;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class VersionCommand extends GeyserCommand {
|
||||
@ -72,29 +71,38 @@ public class VersionCommand extends GeyserCommand {
|
||||
GeyserImpl.NAME, GeyserImpl.VERSION, javaVersions, bedrockVersions));
|
||||
|
||||
// Disable update checking in dev mode and for players in Geyser Standalone
|
||||
if (GeyserImpl.getInstance().isProductionEnvironment() && !(!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) {
|
||||
if (!GeyserImpl.getInstance().isProductionEnvironment() || (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GeyserImpl.IS_DEV) {
|
||||
// TODO cloud use language string
|
||||
sender.sendMessage("You are running a development build of Geyser! Please report any bugs you find on our Discord server: %s"
|
||||
.formatted("https://discord.gg/geysermc"));
|
||||
//sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.core.dev_build", sender.locale(), "https://discord.gg/geysermc"));
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.locale()));
|
||||
try {
|
||||
String buildXML = WebUtils.getBody("https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/" +
|
||||
URLEncoder.encode(GeyserImpl.BRANCH, StandardCharsets.UTF_8) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber");
|
||||
if (buildXML.startsWith("<buildNumber>")) {
|
||||
int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim());
|
||||
int buildNum = this.geyser.buildNumber();
|
||||
if (latestBuildNum == buildNum) {
|
||||
int buildNumber = this.geyser.buildNumber();
|
||||
JsonNode response = WebUtils.getJson("https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest");
|
||||
int latestBuildNumber = response.get("build").asInt();
|
||||
|
||||
if (latestBuildNumber == buildNumber) {
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.locale()));
|
||||
} else {
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.outdated",
|
||||
sender.locale(), (latestBuildNum - buildNum), Constants.GEYSER_DOWNLOAD_LOCATION));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
throw new AssertionError("buildNumber missing");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString(
|
||||
"geyser.commands.version.outdated",
|
||||
sender.locale(), (latestBuildNumber - buildNumber), "https://geysermc.org/download"
|
||||
));
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.version.failed"), e);
|
||||
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", sender.locale()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -72,8 +72,10 @@ public interface GeyserConfiguration {
|
||||
|
||||
boolean isDebugMode();
|
||||
|
||||
@Deprecated
|
||||
boolean isAllowThirdPartyCapes();
|
||||
|
||||
@Deprecated
|
||||
boolean isAllowThirdPartyEars();
|
||||
|
||||
String getShowCooldown();
|
||||
|
@ -94,7 +94,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
||||
private boolean debugMode = false;
|
||||
|
||||
@JsonProperty("allow-third-party-capes")
|
||||
private boolean allowThirdPartyCapes = true;
|
||||
private boolean allowThirdPartyCapes = false;
|
||||
|
||||
@JsonProperty("show-cooldown")
|
||||
private String showCooldown = "title";
|
||||
|
@ -47,6 +47,7 @@ import java.util.function.BiConsumer;
|
||||
* metadata translators needed to translate the properties sent from the server. The translators are structured in such
|
||||
* a way that inserting a new one (for example in version updates) is convenient.
|
||||
*
|
||||
* @param identifier the Bedrock identifier of this entity
|
||||
* @param <T> the entity type this definition represents
|
||||
*/
|
||||
public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, EntityType entityType, String identifier,
|
||||
|
@ -25,6 +25,9 @@
|
||||
|
||||
package org.geysermc.geyser.entity;
|
||||
|
||||
import org.geysermc.geyser.entity.type.AbstractWindChargeEntity;
|
||||
import org.geysermc.geyser.entity.factory.EntityFactory;
|
||||
import org.geysermc.geyser.entity.type.living.monster.raid.RavagerEntity;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
|
||||
@ -62,6 +65,9 @@ public final class EntityDefinitions {
|
||||
public static final EntityDefinition<BeeEntity> BEE;
|
||||
public static final EntityDefinition<BlazeEntity> BLAZE;
|
||||
public static final EntityDefinition<BoatEntity> BOAT;
|
||||
public static final EntityDefinition<BoggedEntity> BOGGED;
|
||||
public static final EntityDefinition<BreezeEntity> BREEZE;
|
||||
public static final EntityDefinition<AbstractWindChargeEntity> BREEZE_WIND_CHARGE;
|
||||
public static final EntityDefinition<CamelEntity> CAMEL;
|
||||
public static final EntityDefinition<CatEntity> CAT;
|
||||
public static final EntityDefinition<SpiderEntity> CAVE_SPIDER;
|
||||
@ -132,7 +138,7 @@ public final class EntityDefinitions {
|
||||
public static final EntityDefinition<ThrownPotionEntity> POTION;
|
||||
public static final EntityDefinition<PufferFishEntity> PUFFERFISH;
|
||||
public static final EntityDefinition<RabbitEntity> RABBIT;
|
||||
public static final EntityDefinition<RaidParticipantEntity> RAVAGER;
|
||||
public static final EntityDefinition<RavagerEntity> RAVAGER;
|
||||
public static final EntityDefinition<AbstractFishEntity> SALMON;
|
||||
public static final EntityDefinition<SheepEntity> SHEEP;
|
||||
public static final EntityDefinition<ShulkerEntity> SHULKER;
|
||||
@ -164,6 +170,7 @@ public final class EntityDefinitions {
|
||||
public static final EntityDefinition<VindicatorEntity> VINDICATOR;
|
||||
public static final EntityDefinition<AbstractMerchantEntity> WANDERING_TRADER;
|
||||
public static final EntityDefinition<WardenEntity> WARDEN;
|
||||
public static final EntityDefinition<AbstractWindChargeEntity> WIND_CHARGE;
|
||||
public static final EntityDefinition<RaidParticipantEntity> WITCH;
|
||||
public static final EntityDefinition<WitherEntity> WITHER;
|
||||
public static final EntityDefinition<AbstractSkeletonEntity> WITHER_SKELETON;
|
||||
@ -234,7 +241,7 @@ public final class EntityDefinitions {
|
||||
.addTranslator(MetadataType.BOOLEAN,
|
||||
(enderCrystalEntity, entityMetadata) -> enderCrystalEntity.setFlag(EntityFlag.SHOW_BOTTOM, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) // There is a base located on the ender crystal
|
||||
.build();
|
||||
EXPERIENCE_ORB = EntityDefinition.<ExpOrbEntity>inherited(null, entityBase)
|
||||
EXPERIENCE_ORB = EntityDefinition.inherited(ExpOrbEntity::new, entityBase)
|
||||
.type(EntityType.EXPERIENCE_ORB)
|
||||
.identifier("minecraft:xp_orb")
|
||||
.build();
|
||||
@ -296,6 +303,7 @@ public final class EntityDefinitions {
|
||||
TNT = EntityDefinition.inherited(TNTEntity::new, entityBase)
|
||||
.type(EntityType.TNT)
|
||||
.heightAndWidth(0.98f)
|
||||
.offset(0.49f)
|
||||
.addTranslator(MetadataType.INT, TNTEntity::setFuseLength)
|
||||
.build();
|
||||
|
||||
@ -373,6 +381,18 @@ public final class EntityDefinitions {
|
||||
.heightAndWidth(0.25f)
|
||||
.build();
|
||||
|
||||
EntityFactory<AbstractWindChargeEntity> windChargeSupplier = AbstractWindChargeEntity::new;
|
||||
BREEZE_WIND_CHARGE = EntityDefinition.inherited(windChargeSupplier, entityBase)
|
||||
.type(EntityType.BREEZE_WIND_CHARGE)
|
||||
.identifier("minecraft:breeze_wind_charge_projectile")
|
||||
.heightAndWidth(0.3125f)
|
||||
.build();
|
||||
WIND_CHARGE = EntityDefinition.inherited(windChargeSupplier, entityBase)
|
||||
.type(EntityType.WIND_CHARGE)
|
||||
.identifier("minecraft:wind_charge_projectile")
|
||||
.heightAndWidth(0.3125f)
|
||||
.build();
|
||||
|
||||
EntityDefinition<AbstractArrowEntity> abstractArrowBase = EntityDefinition.inherited(AbstractArrowEntity::new, entityBase)
|
||||
.addTranslator(MetadataType.BYTE, AbstractArrowEntity::setArrowFlags)
|
||||
.addTranslator(null) // "Piercing level"
|
||||
@ -501,11 +521,20 @@ public final class EntityDefinitions {
|
||||
.height(0.9f).width(0.5f)
|
||||
.addTranslator(MetadataType.BYTE, BatEntity::setBatFlags)
|
||||
.build();
|
||||
BOGGED = EntityDefinition.inherited(BoggedEntity::new, mobEntityBase)
|
||||
.type(EntityType.BOGGED)
|
||||
.height(1.99f).width(0.6f)
|
||||
.addTranslator(MetadataType.BOOLEAN, BoggedEntity::setSheared)
|
||||
.build();
|
||||
BLAZE = EntityDefinition.inherited(BlazeEntity::new, mobEntityBase)
|
||||
.type(EntityType.BLAZE)
|
||||
.height(1.8f).width(0.6f)
|
||||
.addTranslator(MetadataType.BYTE, BlazeEntity::setBlazeFlags)
|
||||
.build();
|
||||
BREEZE = EntityDefinition.inherited(BreezeEntity::new, mobEntityBase)
|
||||
.type(EntityType.BREEZE)
|
||||
.height(1.77f).width(0.6f)
|
||||
.build();
|
||||
CREEPER = EntityDefinition.inherited(CreeperEntity::new, mobEntityBase)
|
||||
.type(EntityType.CREEPER)
|
||||
.height(1.7f).width(0.6f)
|
||||
@ -745,9 +774,9 @@ public final class EntityDefinitions {
|
||||
.type(EntityType.PILLAGER)
|
||||
.height(1.8f).width(0.6f)
|
||||
.offset(1.62f)
|
||||
.addTranslator(null) // Charging; doesn't have an equivalent on Bedrock //TODO check
|
||||
.addTranslator(MetadataType.BOOLEAN, PillagerEntity::setChargingCrossbow)
|
||||
.build();
|
||||
RAVAGER = EntityDefinition.inherited(raidParticipantEntityBase.factory(), raidParticipantEntityBase)
|
||||
RAVAGER = EntityDefinition.inherited(RavagerEntity::new, raidParticipantEntityBase)
|
||||
.type(EntityType.RAVAGER)
|
||||
.height(1.9f).width(1.2f)
|
||||
.build();
|
||||
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Note that, as of 1.21, a wind charge entity does not actually implement the thrown item. We're just reusing
|
||||
* the "hide until far away" aspect.
|
||||
*/
|
||||
public class AbstractWindChargeEntity extends ThrowableItemEntity {
|
||||
public AbstractWindChargeEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getDrag() {
|
||||
// Always, even in water. As of 1.21.
|
||||
return 1f;
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ package org.geysermc.geyser.entity.type;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.item.TippedArrowPotion;
|
||||
import org.geysermc.geyser.inventory.item.Potion;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
|
||||
@ -46,12 +46,7 @@ public class ArrowEntity extends AbstractArrowEntity {
|
||||
if (potionColor == -1) {
|
||||
dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) 0);
|
||||
} else {
|
||||
TippedArrowPotion potion = TippedArrowPotion.getByJavaColor(potionColor);
|
||||
if (potion != null && potion.getJavaColor() != -1) {
|
||||
dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) potion.getBedrockId());
|
||||
} else {
|
||||
dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) 0);
|
||||
}
|
||||
dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, Potion.toTippedArrowId(potionColor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class BoatEntity extends Entity implements Tickable {
|
||||
public class BoatEntity extends Entity implements Leashable, Tickable {
|
||||
|
||||
/**
|
||||
* Required when IS_BUOYANT is sent in order for boats to work in the water. <br>
|
||||
@ -65,6 +65,8 @@ public class BoatEntity extends Entity implements Tickable {
|
||||
@Getter
|
||||
private int variant;
|
||||
|
||||
private long leashHolderBedrockId = -1;
|
||||
|
||||
// Looks too fast and too choppy with 0.1f, which is how I believe the Microsoftian client handles it
|
||||
private final float ROWING_SPEED = 0.1f;
|
||||
|
||||
@ -147,8 +149,18 @@ public class BoatEntity extends Entity implements Tickable {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLeashHolderBedrockId(long bedrockId) {
|
||||
this.leashHolderBedrockId = bedrockId;
|
||||
dirtyMetadata.put(EntityDataTypes.LEASH_HOLDER, bedrockId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InteractiveTag testInteraction(Hand hand) {
|
||||
InteractiveTag tag = super.testInteraction(hand);
|
||||
if (tag != InteractiveTag.NONE) {
|
||||
return tag;
|
||||
}
|
||||
if (session.isSneaking()) {
|
||||
return InteractiveTag.NONE;
|
||||
} else if (passengers.size() < 2) {
|
||||
@ -160,6 +172,10 @@ public class BoatEntity extends Entity implements Tickable {
|
||||
|
||||
@Override
|
||||
public InteractionResult interact(Hand hand) {
|
||||
InteractionResult result = super.interact(hand);
|
||||
if (result != InteractionResult.PASS) {
|
||||
return result;
|
||||
}
|
||||
if (session.isSneaking()) {
|
||||
return InteractionResult.PASS;
|
||||
} else {
|
||||
@ -191,6 +207,11 @@ public class BoatEntity extends Entity implements Tickable {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long leashHolderBedrockId() {
|
||||
return leashHolderBedrockId;
|
||||
}
|
||||
|
||||
private void sendAnimationPacket(GeyserSession session, Entity rower, AnimatePacket.Action action, float rowTime) {
|
||||
AnimatePacket packet = new AnimatePacket();
|
||||
packet.setRuntimeEntityId(rower.getGeyserId());
|
||||
|
@ -40,6 +40,7 @@ import org.geysermc.geyser.api.entity.type.GeyserEntity;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.GeyserDirtyMetadata;
|
||||
import org.geysermc.geyser.entity.properties.GeyserEntityPropertyManager;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.util.EntityUtils;
|
||||
@ -59,6 +60,9 @@ import java.util.*;
|
||||
@Getter
|
||||
@Setter
|
||||
public class Entity implements GeyserEntity {
|
||||
|
||||
private static final boolean PRINT_ENTITY_SPAWN_DEBUG = Boolean.parseBoolean(System.getProperty("Geyser.PrintEntitySpawnDebug", "false"));
|
||||
|
||||
protected final GeyserSession session;
|
||||
|
||||
protected int entityId;
|
||||
@ -134,7 +138,7 @@ public class Entity implements GeyserEntity {
|
||||
|
||||
this.valid = false;
|
||||
|
||||
this.propertyManager = new GeyserEntityPropertyManager(definition.registeredProperties());
|
||||
this.propertyManager = definition.registeredProperties() == null ? null : new GeyserEntityPropertyManager(definition.registeredProperties());
|
||||
|
||||
setPosition(position);
|
||||
setAirSupply(getMaxAir());
|
||||
@ -181,7 +185,7 @@ public class Entity implements GeyserEntity {
|
||||
|
||||
flagsDirty = false;
|
||||
|
||||
if (session.getGeyser().getConfig().isDebugMode()) {
|
||||
if (session.getGeyser().getConfig().isDebugMode() && PRINT_ENTITY_SPAWN_DEBUG) {
|
||||
EntityType type = definition.entityType();
|
||||
String name = type != null ? type.name() : getClass().getSimpleName();
|
||||
session.getGeyser().getLogger().debug("Spawned entity " + name + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");
|
||||
@ -361,7 +365,7 @@ public class Entity implements GeyserEntity {
|
||||
return;
|
||||
}
|
||||
|
||||
if (propertyManager.hasProperties()) {
|
||||
if (propertyManager != null && propertyManager.hasProperties()) {
|
||||
SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
|
||||
entityDataPacket.setRuntimeEntityId(geyserId);
|
||||
propertyManager.applyIntProperties(entityDataPacket.getProperties().getIntProperties());
|
||||
@ -554,6 +558,17 @@ public class Entity implements GeyserEntity {
|
||||
* Should usually mirror {@link #interact(Hand)} without any side effects.
|
||||
*/
|
||||
protected InteractiveTag testInteraction(Hand hand) {
|
||||
if (isAlive() && this instanceof Leashable leashable) {
|
||||
if (leashable.leashHolderBedrockId() == session.getPlayerEntity().getGeyserId()) {
|
||||
// Note this might be client side. Has yet to be an issue though, as of Java 1.21.
|
||||
return InteractiveTag.REMOVE_LEASH;
|
||||
}
|
||||
if (session.getPlayerInventory().getItemInHand(hand).asItem() == Items.LEAD && leashable.canBeLeashed()) {
|
||||
// We shall leash
|
||||
return InteractiveTag.LEASH;
|
||||
}
|
||||
}
|
||||
|
||||
return InteractiveTag.NONE;
|
||||
}
|
||||
|
||||
@ -562,6 +577,18 @@ public class Entity implements GeyserEntity {
|
||||
* to ensure packet parity as well as functionality parity (such as sound effect responses).
|
||||
*/
|
||||
public InteractionResult interact(Hand hand) {
|
||||
if (isAlive() && this instanceof Leashable leashable) {
|
||||
if (leashable.leashHolderBedrockId() == session.getPlayerEntity().getGeyserId()) {
|
||||
// Note this might also update client side (a theoretical Geyser/client desync and Java parity issue).
|
||||
// Has yet to be an issue though, as of Java 1.21.
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
if (session.getPlayerInventory().getItemInHand(hand).asItem() == Items.LEAD && leashable.canBeLeashed()) {
|
||||
// We shall leash
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
|
@ -27,11 +27,18 @@ package org.geysermc.geyser.entity.type;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ExpOrbEntity extends Entity {
|
||||
|
||||
public ExpOrbEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> entityDefinition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
this(session, 1, entityId, geyserId, position);
|
||||
}
|
||||
|
||||
public ExpOrbEntity(GeyserSession session, int amount, int entityId, long geyserId, Vector3f position) {
|
||||
super(session, entityId, geyserId, null, EntityDefinitions.EXPERIENCE_ORB, position, Vector3f.ZERO, 0, 0, 0);
|
||||
|
||||
|
@ -33,6 +33,7 @@ import org.geysermc.erosion.util.BlockPositionIterator;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.block.type.Block;
|
||||
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.collision.BlockCollision;
|
||||
@ -162,7 +163,7 @@ public class FishingHookEntity extends ThrowableEntity {
|
||||
*/
|
||||
protected boolean isInAir() {
|
||||
int block = session.getGeyser().getWorldManager().getBlockAt(session, position.toInt());
|
||||
return block == BlockStateValues.JAVA_AIR_ID;
|
||||
return block == Block.JAVA_AIR_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -25,14 +25,16 @@
|
||||
|
||||
package org.geysermc.geyser.entity.type;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.block.Blocks;
|
||||
import org.geysermc.geyser.level.block.property.Properties;
|
||||
import org.geysermc.geyser.level.block.type.BlockState;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@ -51,7 +53,8 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity {
|
||||
|
||||
@Override
|
||||
public void updateDefaultBlockMetadata() {
|
||||
dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(hasFuel ? BlockStateValues.JAVA_FURNACE_LIT_ID : BlockStateValues.JAVA_FURNACE_ID));
|
||||
BlockState furnace = Blocks.FURNACE.defaultBlockState().withValue(Properties.LIT, hasFuel);
|
||||
dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(furnace));
|
||||
dirtyMetadata.put(EntityDataTypes.DISPLAY_OFFSET, 6);
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,10 @@ public class InteractionEntity extends Entity {
|
||||
}
|
||||
|
||||
public void setHeight(FloatEntityMetadata height) {
|
||||
setBoundingBoxHeight(height.getPrimitiveValue());
|
||||
// Bedrock does *not* like high values being placed here
|
||||
// https://gist.github.com/Owen1212055/f5d59169d3a6a5c32f0c173d57eb199d recommend(s/ed) using the tactic
|
||||
// https://github.com/GeyserMC/Geyser/issues/4688
|
||||
setBoundingBoxHeight(Math.min(height.getPrimitiveValue(), 64f));
|
||||
}
|
||||
|
||||
public void setResponse(BooleanEntityMetadata response) {
|
||||
|
@ -34,6 +34,7 @@ import org.cloudburstmc.protocol.bedrock.packet.AddItemEntityPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.block.type.BlockState;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.ItemTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
@ -137,7 +138,7 @@ public class ItemEntity extends ThrowableEntity {
|
||||
protected float getDrag() {
|
||||
if (isOnGround()) {
|
||||
Vector3i groundBlockPos = position.toInt().down(1);
|
||||
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, groundBlockPos);
|
||||
BlockState blockState = session.getGeyser().getWorldManager().blockAt(session, groundBlockPos);
|
||||
return BlockStateValues.getSlipperiness(blockState) * 0.98f;
|
||||
}
|
||||
return 0.98f;
|
||||
|
44
core/src/main/java/org/geysermc/geyser/entity/type/Leashable.java
Normale Datei
44
core/src/main/java/org/geysermc/geyser/entity/type/Leashable.java
Normale Datei
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type;
|
||||
|
||||
/**
|
||||
* I can haz lead
|
||||
* (The item, not the mineral)
|
||||
*/
|
||||
public interface Leashable {
|
||||
void setLeashHolderBedrockId(long bedrockId);
|
||||
|
||||
long leashHolderBedrockId();
|
||||
|
||||
default boolean canBeLeashed() {
|
||||
return isNotLeashed();
|
||||
}
|
||||
|
||||
default boolean isNotLeashed() {
|
||||
return leashHolderBedrockId() == -1L;
|
||||
}
|
||||
}
|
@ -352,6 +352,15 @@ public class LivingEntity extends Entity {
|
||||
session.sendUpstreamPacket(offHandPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a SWING_ARM animation packet is received
|
||||
*
|
||||
* @return true if an ATTACK_START event should be used instead
|
||||
*/
|
||||
public boolean useArmSwingAttack() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attributes are properties of an entity that are generally more runtime-based instead of permanent properties.
|
||||
* Movement speed, current attack damage with a weapon, current knockback resistance.
|
||||
|
@ -30,6 +30,8 @@ import org.cloudburstmc.protocol.bedrock.packet.AddPaintingPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.level.PaintingType;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.PaintingVariant;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
|
||||
|
||||
@ -49,8 +51,14 @@ public class PaintingEntity extends Entity {
|
||||
// Wait until we get the metadata needed
|
||||
}
|
||||
|
||||
public void setPaintingType(ObjectEntityMetadata<org.geysermc.mcprotocollib.protocol.data.game.entity.type.PaintingType> entityMetadata) {
|
||||
PaintingType type = PaintingType.getByPaintingType(entityMetadata.getValue());
|
||||
public void setPaintingType(ObjectEntityMetadata<Holder<PaintingVariant>> entityMetadata) {
|
||||
if (!entityMetadata.getValue().isId()) {
|
||||
return;
|
||||
}
|
||||
PaintingType type = session.getRegistryCache().paintings().byId(entityMetadata.getValue().id());
|
||||
if (type == null) {
|
||||
return;
|
||||
}
|
||||
AddPaintingPacket addPaintingPacket = new AddPaintingPacket();
|
||||
addPaintingPacket.setUniqueEntityId(geyserId);
|
||||
addPaintingPacket.setRuntimeEntityId(geyserId);
|
||||
@ -78,8 +86,12 @@ public class PaintingEntity extends Entity {
|
||||
|
||||
private Vector3f fixOffset(PaintingType paintingName) {
|
||||
Vector3f position = super.position;
|
||||
// ViaVersion already adds the offset for us on older versions,
|
||||
// so no need to do it then otherwise it will be spaced
|
||||
if (session.isEmulatePost1_18Logic()) {
|
||||
position = position.add(0.5, 0.5, 0.5);
|
||||
double widthOffset = paintingName.getWidth() > 1 ? 0.5 : 0;
|
||||
}
|
||||
double widthOffset = paintingName.getWidth() > 1 && paintingName.getWidth() != 3 ? 0.5 : 0;
|
||||
double heightOffset = paintingName.getHeight() > 1 && paintingName.getHeight() != 3 ? 0.5 : 0;
|
||||
|
||||
return switch (direction) {
|
||||
|
@ -28,7 +28,7 @@ package org.geysermc.geyser.entity.type;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.block.Blocks;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.UUID;
|
||||
@ -41,7 +41,7 @@ public class SpawnerMinecartEntity extends DefaultBlockMinecartEntity {
|
||||
|
||||
@Override
|
||||
public void updateDefaultBlockMetadata() {
|
||||
dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(BlockStateValues.JAVA_SPAWNER_ID));
|
||||
dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(Blocks.SPAWNER.defaultBlockState()));
|
||||
dirtyMetadata.put(EntityDataTypes.DISPLAY_OFFSET, 6);
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,17 @@ public class TNTEntity extends Entity implements Tickable {
|
||||
private int currentTick;
|
||||
|
||||
public TNTEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
super(session, entityId, geyserId, uuid, definition, position.add(0, definition.offset(), 0), motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) {
|
||||
super.moveRelative(relX, relY + definition.offset(), relZ, yaw, pitch, isOnGround);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
|
||||
super.moveAbsolute(position.add(Vector3f.from(0, definition.offset(), 0)), yaw, pitch, headYaw, isOnGround, teleported);
|
||||
}
|
||||
|
||||
public void setFuseLength(IntEntityMetadata entityMetadata) {
|
||||
|
@ -38,7 +38,7 @@ public class AmbientEntity extends MobEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
public boolean canBeLeashed() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ public class DolphinEntity extends WaterEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
public boolean canBeLeashed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -25,12 +25,12 @@
|
||||
|
||||
package org.geysermc.geyser.entity.type.living;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.type.Leashable;
|
||||
import org.geysermc.geyser.entity.type.LivingEntity;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
@ -43,11 +43,10 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class MobEntity extends LivingEntity {
|
||||
public class MobEntity extends LivingEntity implements Leashable {
|
||||
/**
|
||||
* If another mob is holding this mob by a leash, this variable tracks their Bedrock entity ID.
|
||||
*/
|
||||
@Getter
|
||||
private long leashHolderBedrockId;
|
||||
|
||||
public MobEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
@ -65,6 +64,7 @@ public class MobEntity extends LivingEntity {
|
||||
setFlag(EntityFlag.NO_AI, (xd & 0x01) == 0x01);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLeashHolderBedrockId(long bedrockId) {
|
||||
this.leashHolderBedrockId = bedrockId;
|
||||
dirtyMetadata.put(EntityDataTypes.LEASH_HOLDER, bedrockId);
|
||||
@ -79,10 +79,7 @@ public class MobEntity extends LivingEntity {
|
||||
return InteractiveTag.REMOVE_LEASH;
|
||||
} else {
|
||||
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(hand);
|
||||
if (itemStack.asItem() == Items.LEAD && canBeLeashed()) {
|
||||
// We shall leash
|
||||
return InteractiveTag.LEASH;
|
||||
} else if (itemStack.asItem() == Items.NAME_TAG) {
|
||||
if (itemStack.asItem() == Items.NAME_TAG) {
|
||||
InteractionResult result = checkInteractWithNameTag(itemStack);
|
||||
if (result.consumesAction()) {
|
||||
return InteractiveTag.NAME;
|
||||
@ -99,9 +96,6 @@ public class MobEntity extends LivingEntity {
|
||||
if (!isAlive()) {
|
||||
// dead lol
|
||||
return InteractionResult.PASS;
|
||||
} else if (leashHolderBedrockId == session.getPlayerEntity().getGeyserId()) {
|
||||
// TODO looks like the client assumes it will go through and removes the attachment itself?
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand(hand);
|
||||
InteractionResult result = checkPriorityInteractions(itemInHand);
|
||||
@ -115,10 +109,7 @@ public class MobEntity extends LivingEntity {
|
||||
}
|
||||
|
||||
private InteractionResult checkPriorityInteractions(GeyserItemStack itemInHand) {
|
||||
if (itemInHand.asItem() == Items.LEAD && canBeLeashed()) {
|
||||
// We shall leash
|
||||
return InteractionResult.SUCCESS;
|
||||
} else if (itemInHand.asItem() == Items.NAME_TAG) {
|
||||
if (itemInHand.asItem() == Items.NAME_TAG) {
|
||||
InteractionResult result = checkInteractWithNameTag(itemInHand);
|
||||
if (result.consumesAction()) {
|
||||
return result;
|
||||
@ -143,12 +134,14 @@ public class MobEntity extends LivingEntity {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
protected boolean canBeLeashed() {
|
||||
@Override
|
||||
public boolean canBeLeashed() {
|
||||
return isNotLeashed() && !isEnemy();
|
||||
}
|
||||
|
||||
protected final boolean isNotLeashed() {
|
||||
return leashHolderBedrockId == -1L;
|
||||
@Override
|
||||
public long leashHolderBedrockId() {
|
||||
return leashHolderBedrockId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,7 +122,7 @@ public class SquidEntity extends WaterEntity implements Tickable {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
public boolean canBeLeashed() {
|
||||
return isNotLeashed();
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ public class WaterEntity extends CreatureEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
public boolean canBeLeashed() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ public class AxolotlEntity extends AnimalEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
public boolean canBeLeashed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ package org.geysermc.geyser.entity.type.living.animal;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
@ -40,6 +41,8 @@ public class HoglinEntity extends AnimalEntity {
|
||||
|
||||
public HoglinEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
dirtyMetadata.put(EntityDataTypes.TARGET_EID, session.getPlayerEntity().getGeyserId());
|
||||
setFlag(EntityFlag.SHAKING, isShaking());
|
||||
}
|
||||
|
||||
public void setImmuneToZombification(BooleanEntityMetadata entityMetadata) {
|
||||
@ -60,7 +63,7 @@ public class HoglinEntity extends AnimalEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
public boolean canBeLeashed() {
|
||||
return isNotLeashed();
|
||||
}
|
||||
|
||||
@ -68,4 +71,9 @@ public class HoglinEntity extends AnimalEntity {
|
||||
protected boolean isEnemy() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useArmSwingAttack() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ public class PandaEntity extends AnimalEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
public boolean canBeLeashed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@ public class TurtleEntity extends AnimalEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
public boolean canBeLeashed() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ public abstract class TameableEntity extends AnimalEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
public boolean canBeLeashed() {
|
||||
return isNotLeashed();
|
||||
}
|
||||
}
|
||||
|
@ -33,36 +33,28 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.item.Enchantment;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.enchantment.EnchantmentComponent;
|
||||
import org.geysermc.geyser.item.type.DyeItem;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
import org.geysermc.geyser.util.ItemUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.WolfVariant;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class WolfEntity extends TameableEntity {
|
||||
/**
|
||||
* A list of all foods a wolf can eat on Java Edition.
|
||||
* Used to display interactive tag or particles if needed.
|
||||
* TODO generate
|
||||
*/
|
||||
private static final Set<Item> WOLF_FOODS = Set.of(Items.PUFFERFISH, Items.TROPICAL_FISH, Items.CHICKEN, Items.COOKED_CHICKEN,
|
||||
Items.PORKCHOP, Items.BEEF, Items.RABBIT, Items.COOKED_PORKCHOP, Items.COOKED_BEEF, Items.ROTTEN_FLESH, Items.MUTTON, Items.COOKED_MUTTON,
|
||||
Items.COOKED_RABBIT);
|
||||
|
||||
private byte collarColor = 14; // Red - default
|
||||
|
||||
private boolean isCurseOfBinding = false;
|
||||
@ -112,12 +104,14 @@ public class WolfEntity extends TameableEntity {
|
||||
}
|
||||
|
||||
// 1.20.5+
|
||||
public void setWolfVariant(IntEntityMetadata entityMetadata) {
|
||||
WolfVariant wolfVariant = session.getRegistryCache().wolfVariants().byId(entityMetadata.getPrimitiveValue());
|
||||
public void setWolfVariant(ObjectEntityMetadata<Holder<WolfVariant>> entityMetadata) {
|
||||
entityMetadata.getValue().ifId(id -> {
|
||||
BuiltInWolfVariant wolfVariant = session.getRegistryCache().wolfVariants().byId(id);
|
||||
if (wolfVariant == null) {
|
||||
wolfVariant = WolfVariant.PALE;
|
||||
wolfVariant = BuiltInWolfVariant.PALE;
|
||||
}
|
||||
dirtyMetadata.put(EntityDataTypes.VARIANT, wolfVariant.ordinal());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -129,11 +123,11 @@ public class WolfEntity extends TameableEntity {
|
||||
@Override
|
||||
public void setChestplate(ItemStack stack) {
|
||||
super.setChestplate(stack);
|
||||
isCurseOfBinding = ItemUtils.getEnchantmentLevel(stack.getDataComponents(), Enchantment.JavaEnchantment.BINDING_CURSE) > 0;
|
||||
isCurseOfBinding = ItemUtils.hasEffect(session, stack, EnchantmentComponent.PREVENT_ARMOR_CHANGE); // TODO test
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
public boolean canBeLeashed() {
|
||||
return !getFlag(EntityFlag.ANGRY) && super.canBeLeashed();
|
||||
}
|
||||
|
||||
@ -187,7 +181,7 @@ public class WolfEntity extends TameableEntity {
|
||||
}
|
||||
|
||||
// Ordered by bedrock id
|
||||
public enum WolfVariant {
|
||||
public enum BuiltInWolfVariant {
|
||||
PALE,
|
||||
ASHEN,
|
||||
BLACK,
|
||||
@ -198,16 +192,16 @@ public class WolfEntity extends TameableEntity {
|
||||
STRIPED,
|
||||
WOODS;
|
||||
|
||||
private static final WolfVariant[] VALUES = values();
|
||||
private static final BuiltInWolfVariant[] VALUES = values();
|
||||
|
||||
private final String javaIdentifier;
|
||||
|
||||
WolfVariant() {
|
||||
BuiltInWolfVariant() {
|
||||
this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
public static @Nullable WolfVariant getByJavaIdentifier(String javaIdentifier) {
|
||||
for (WolfVariant wolfVariant : VALUES) {
|
||||
public static @Nullable BuiltInWolfVariant getByJavaIdentifier(String javaIdentifier) {
|
||||
for (BuiltInWolfVariant wolfVariant : VALUES) {
|
||||
if (wolfVariant.javaIdentifier.equals(javaIdentifier)) {
|
||||
return wolfVariant;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ public class AbstractMerchantEntity extends AgeableEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
public boolean canBeLeashed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -33,8 +33,9 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MoveEntityAbsolutePacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.type.BlockMapping;
|
||||
import org.geysermc.geyser.level.block.property.Properties;
|
||||
import org.geysermc.geyser.level.block.type.BedBlock;
|
||||
import org.geysermc.geyser.level.block.type.BlockState;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.VillagerData;
|
||||
@ -119,29 +120,32 @@ public class VillagerEntity extends AbstractMerchantEntity {
|
||||
}
|
||||
|
||||
// The bed block
|
||||
int blockId = session.getGeyser().getWorldManager().getBlockAt(session, bedPosition);
|
||||
String fullIdentifier = BlockRegistries.JAVA_BLOCKS.getOrDefault(blockId, BlockMapping.DEFAULT).getJavaIdentifier();
|
||||
BlockState state = session.getGeyser().getWorldManager().blockAt(session, bedPosition);
|
||||
|
||||
// Set the correct position offset and rotation when sleeping
|
||||
int bedRotation = 0;
|
||||
float xOffset = 0;
|
||||
float zOffset = 0;
|
||||
if (fullIdentifier.contains("facing=south")) {
|
||||
// bed is facing south
|
||||
if (state.block() instanceof BedBlock) {
|
||||
switch (state.getValue(Properties.HORIZONTAL_FACING)) {
|
||||
case SOUTH -> {
|
||||
bedRotation = 180;
|
||||
zOffset = -.5f;
|
||||
} else if (fullIdentifier.contains("facing=east")) {
|
||||
// bed is facing east
|
||||
}
|
||||
case EAST -> {
|
||||
bedRotation = 90;
|
||||
xOffset = -.5f;
|
||||
} else if (fullIdentifier.contains("facing=west")) {
|
||||
// bed is facing west
|
||||
}
|
||||
case WEST -> {
|
||||
bedRotation = 270;
|
||||
xOffset = .5f;
|
||||
} else if (fullIdentifier.contains("facing=north")) {
|
||||
}
|
||||
case NORTH -> {
|
||||
// rotation does not change because north is 0
|
||||
zOffset = .5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setYaw(yaw);
|
||||
setPitch(pitch);
|
||||
|
@ -26,7 +26,9 @@
|
||||
package org.geysermc.geyser.entity.type.living.monster;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||
@ -45,5 +47,17 @@ public class AbstractSkeletonEntity extends MonsterEntity {
|
||||
byte xd = entityMetadata.getPrimitiveValue();
|
||||
// A bit of a loophole so the hands get raised - set the target ID to its own ID
|
||||
dirtyMetadata.put(EntityDataTypes.TARGET_EID, ((xd & 4) == 4) ? geyserId : 0);
|
||||
|
||||
if ((xd & 4) == 4) {
|
||||
ItemDefinition bow = session.getItemMappings().getStoredItems().bow().getBedrockDefinition();
|
||||
setFlag(EntityFlag.FACING_TARGET_TO_RANGE_ATTACK, this.hand.getDefinition() == bow || this.offhand.getDefinition() == bow);
|
||||
} else {
|
||||
setFlag(EntityFlag.FACING_TARGET_TO_RANGE_ATTACK, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useArmSwingAttack() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +26,13 @@
|
||||
package org.geysermc.geyser.entity.type.living.monster;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@ -38,6 +41,16 @@ public class BasePiglinEntity extends MonsterEntity {
|
||||
|
||||
public BasePiglinEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
// Both TARGET_EID and BLOCK are needed for melee attack animation
|
||||
dirtyMetadata.put(EntityDataTypes.BLOCK, session.getBlockMappings().getDefinition(1));
|
||||
setFlag(EntityFlag.SHAKING, isShaking());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMobFlags(ByteEntityMetadata entityMetadata) {
|
||||
super.setMobFlags(entityMetadata);
|
||||
byte xd = entityMetadata.getPrimitiveValue();
|
||||
dirtyMetadata.put(EntityDataTypes.TARGET_EID, (xd & 4) == 4 ? session.getPlayerEntity().getGeyserId() : 0);
|
||||
}
|
||||
|
||||
public void setImmuneToZombification(BooleanEntityMetadata entityMetadata) {
|
||||
@ -50,4 +63,9 @@ public class BasePiglinEntity extends MonsterEntity {
|
||||
protected boolean isShaking() {
|
||||
return (!isImmuneToZombification && !session.getDimensionType().piglinSafe()) || super.isShaking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useArmSwingAttack() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type.living.monster;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class BoggedEntity extends AbstractSkeletonEntity {
|
||||
private boolean sheared = false;
|
||||
|
||||
public BoggedEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
public void setSheared(BooleanEntityMetadata entityMetadata) {
|
||||
this.sheared = entityMetadata.getPrimitiveValue();
|
||||
setFlag(EntityFlag.SHEARED, this.sheared);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull InteractiveTag testMobInteraction(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) {
|
||||
if (itemInHand.asItem() == Items.SHEARS && readyForShearing()) {
|
||||
return InteractiveTag.SHEAR;
|
||||
}
|
||||
return super.testMobInteraction(hand, itemInHand);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull InteractionResult mobInteract(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) {
|
||||
if (itemInHand.asItem() == Items.SHEARS && readyForShearing()) {
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
return super.mobInteract(hand, itemInHand);
|
||||
}
|
||||
|
||||
private boolean readyForShearing() {
|
||||
return !this.sheared && this.isAlive();
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type.living.monster;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class BreezeEntity extends MonsterEntity {
|
||||
public BreezeEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPose(Pose pose) {
|
||||
// TODO Test
|
||||
setFlag(EntityFlag.FACING_TARGET_TO_RANGE_ATTACK, pose == Pose.SHOOTING);
|
||||
setFlag(EntityFlag.JUMP_GOAL_JUMP, pose == Pose.INHALING);
|
||||
super.setPose(pose);
|
||||
}
|
||||
}
|
@ -27,16 +27,22 @@ package org.geysermc.geyser.entity.type.living.monster;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MobEquipmentPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@ -55,13 +61,61 @@ public class PiglinEntity extends BasePiglinEntity {
|
||||
}
|
||||
|
||||
public void setChargingCrossbow(BooleanEntityMetadata entityMetadata) {
|
||||
setFlag(EntityFlag.CHARGING, entityMetadata.getPrimitiveValue());
|
||||
boolean charging = entityMetadata.getPrimitiveValue();
|
||||
setFlag(EntityFlag.CHARGING, charging);
|
||||
dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, charging ? (byte) 64 : (byte) 0); // TODO: gradually increase
|
||||
}
|
||||
|
||||
public void setDancing(BooleanEntityMetadata entityMetadata) {
|
||||
setFlag(EntityFlag.DANCING, entityMetadata.getPrimitiveValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHand(ItemStack stack) {
|
||||
ItemMapping crossbow = session.getItemMappings().getStoredItems().crossbow();
|
||||
boolean toCrossbow = stack != null && stack.getId() == crossbow.getJavaItem().javaId();
|
||||
|
||||
if (toCrossbow ^ this.hand.getDefinition() == crossbow.getBedrockDefinition()) { // If switching to/from crossbow
|
||||
dirtyMetadata.put(EntityDataTypes.BLOCK, session.getBlockMappings().getDefinition(toCrossbow ? 0 : 1));
|
||||
dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, (byte) 0);
|
||||
setFlag(EntityFlag.CHARGED, false);
|
||||
setFlag(EntityFlag.USING_ITEM, false);
|
||||
updateBedrockMetadata();
|
||||
|
||||
if (this.hand.isValid()) {
|
||||
MobEquipmentPacket mobEquipmentPacket = new MobEquipmentPacket();
|
||||
mobEquipmentPacket.setRuntimeEntityId(geyserId);
|
||||
mobEquipmentPacket.setContainerId(ContainerId.INVENTORY);
|
||||
mobEquipmentPacket.setInventorySlot(0);
|
||||
mobEquipmentPacket.setHotbarSlot(-1);
|
||||
mobEquipmentPacket.setItem(ItemData.AIR);
|
||||
session.sendUpstreamPacket(mobEquipmentPacket);
|
||||
}
|
||||
}
|
||||
|
||||
super.setHand(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMainHand(GeyserSession session) {
|
||||
super.updateMainHand(session);
|
||||
|
||||
if (this.hand.getDefinition() == session.getItemMappings().getStoredItems().crossbow().getBedrockDefinition()) {
|
||||
if (this.hand.getTag() != null && this.hand.getTag().containsKey("chargedItem")) {
|
||||
dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, Byte.MAX_VALUE);
|
||||
setFlag(EntityFlag.CHARGING, false);
|
||||
setFlag(EntityFlag.CHARGED, true);
|
||||
setFlag(EntityFlag.USING_ITEM, true);
|
||||
} else if (getFlag(EntityFlag.CHARGED)) {
|
||||
dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, (byte) 0);
|
||||
setFlag(EntityFlag.CHARGED, false);
|
||||
setFlag(EntityFlag.USING_ITEM, false);
|
||||
}
|
||||
}
|
||||
|
||||
updateBedrockMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOffHand(GeyserSession session) {
|
||||
// Check if the Piglin is holding Gold and set the ADMIRING flag accordingly so its pose updates
|
||||
|
@ -26,6 +26,7 @@
|
||||
package org.geysermc.geyser.entity.type.living.monster;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
@ -37,6 +38,7 @@ public class ZoglinEntity extends MonsterEntity {
|
||||
|
||||
public ZoglinEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
dirtyMetadata.put(EntityDataTypes.TARGET_EID, session.getPlayerEntity().getGeyserId());
|
||||
}
|
||||
|
||||
public void setBaby(BooleanEntityMetadata entityMetadata) {
|
||||
@ -56,7 +58,7 @@ public class ZoglinEntity extends MonsterEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
public boolean canBeLeashed() {
|
||||
return isNotLeashed();
|
||||
}
|
||||
|
||||
@ -64,4 +66,9 @@ public class ZoglinEntity extends MonsterEntity {
|
||||
protected boolean isEnemy() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useArmSwingAttack() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -57,4 +57,9 @@ public class ZombieEntity extends MonsterEntity {
|
||||
protected boolean isShaking() {
|
||||
return convertingToDrowned || super.isShaking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useArmSwingAttack() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +26,13 @@
|
||||
package org.geysermc.geyser.entity.type.living.monster.raid;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@ -39,16 +42,22 @@ public class PillagerEntity extends AbstractIllagerEntity {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
public void setChargingCrossbow(BooleanEntityMetadata entityMetadata) {
|
||||
boolean charging = entityMetadata.getPrimitiveValue();
|
||||
setFlag(EntityFlag.CHARGING, charging);
|
||||
dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, charging ? (byte) 64 : (byte) 0); // TODO: gradually increase
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMainHand(GeyserSession session) { //TODO
|
||||
checkForCrossbow();
|
||||
public void updateMainHand(GeyserSession session) {
|
||||
updateCrossbow();
|
||||
|
||||
super.updateMainHand(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOffHand(GeyserSession session) {
|
||||
checkForCrossbow();
|
||||
updateCrossbow();
|
||||
|
||||
super.updateOffHand(session);
|
||||
}
|
||||
@ -56,12 +65,27 @@ public class PillagerEntity extends AbstractIllagerEntity {
|
||||
/**
|
||||
* Check for a crossbow in either the mainhand or offhand. If one exists, indicate that the pillager should be posing
|
||||
*/
|
||||
protected void checkForCrossbow() {
|
||||
protected void updateCrossbow() {
|
||||
ItemMapping crossbow = session.getItemMappings().getStoredItems().crossbow();
|
||||
boolean hasCrossbow = this.hand.getDefinition() == crossbow.getBedrockDefinition()
|
||||
|| this.offhand.getDefinition() == crossbow.getBedrockDefinition();
|
||||
setFlag(EntityFlag.USING_ITEM, hasCrossbow);
|
||||
setFlag(EntityFlag.CHARGED, hasCrossbow);
|
||||
ItemData activeCrossbow = null;
|
||||
if (this.hand.getDefinition() == crossbow.getBedrockDefinition()) {
|
||||
activeCrossbow = this.hand;
|
||||
} else if (this.offhand.getDefinition() == crossbow.getBedrockDefinition()) {
|
||||
activeCrossbow = this.offhand;
|
||||
}
|
||||
|
||||
if (activeCrossbow != null) {
|
||||
if (activeCrossbow.getTag() != null && activeCrossbow.getTag().containsKey("chargedItem")) {
|
||||
dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, Byte.MAX_VALUE);
|
||||
setFlag(EntityFlag.CHARGING, false);
|
||||
setFlag(EntityFlag.CHARGED, true);
|
||||
setFlag(EntityFlag.USING_ITEM, true);
|
||||
} else if (getFlag(EntityFlag.CHARGED)) {
|
||||
dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, (byte) 0);
|
||||
setFlag(EntityFlag.CHARGED, false);
|
||||
setFlag(EntityFlag.USING_ITEM, false);
|
||||
}
|
||||
}
|
||||
|
||||
updateBedrockMetadata();
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type.living.monster.raid;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RavagerEntity extends RaidParticipantEntity {
|
||||
|
||||
public RavagerEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useArmSwingAttack() {
|
||||
setFlag(EntityFlag.DELAYED_ATTACK, false);
|
||||
updateBedrockMetadata();
|
||||
|
||||
session.scheduleInEventLoop(() -> {
|
||||
setFlag(EntityFlag.DELAYED_ATTACK, true);
|
||||
updateBedrockMetadata();
|
||||
}, 75, TimeUnit.MILLISECONDS);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@
|
||||
package org.geysermc.geyser.entity.type.living.monster.raid;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
@ -37,6 +38,7 @@ public class VindicatorEntity extends AbstractIllagerEntity {
|
||||
|
||||
public VindicatorEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
dirtyMetadata.put(EntityDataTypes.TARGET_EID, session.getPlayerEntity().getGeyserId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -46,4 +48,9 @@ public class VindicatorEntity extends AbstractIllagerEntity {
|
||||
byte xd = entityMetadata.getPrimitiveValue();
|
||||
setFlag(EntityFlag.ANGRY, (xd & 4) == 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useArmSwingAttack() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.*;
|
||||
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
@ -166,6 +167,31 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
session.sendUpstreamPacket(addPlayerPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void despawnEntity() {
|
||||
super.despawnEntity();
|
||||
|
||||
// Since we re-use player entities: Clear flags, held item, etc
|
||||
this.resetMetadata();
|
||||
this.hand = ItemData.AIR;
|
||||
this.offhand = ItemData.AIR;
|
||||
this.boots = ItemData.AIR;
|
||||
this.leggings = ItemData.AIR;
|
||||
this.chestplate = ItemData.AIR;
|
||||
this.helmet = ItemData.AIR;
|
||||
}
|
||||
|
||||
public void resetMetadata() {
|
||||
// Reset all metadata to their default values
|
||||
// This is used when a player respawns
|
||||
this.flags.clear();
|
||||
this.initializeMetadata();
|
||||
|
||||
// Explicitly reset all metadata not handled by initializeMetadata
|
||||
setParrot(null, true);
|
||||
setParrot(null, false);
|
||||
}
|
||||
|
||||
public void sendPlayer() {
|
||||
if (session.getEntityCache().getPlayerEntity(uuid) == null)
|
||||
return;
|
||||
|
@ -27,14 +27,18 @@ package org.geysermc.geyser.entity.type.player;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.level.BedrockDimension;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.AttributeUtils;
|
||||
import org.geysermc.geyser.util.DimensionUtils;
|
||||
@ -60,16 +64,23 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
*/
|
||||
@Getter
|
||||
protected final Map<GeyserAttributeType, AttributeData> attributes = new Object2ObjectOpenHashMap<>();
|
||||
/**
|
||||
* Whether to check for updated speed after all entity metadata has been processed
|
||||
*/
|
||||
private boolean refreshSpeed = false;
|
||||
/**
|
||||
* Used in PlayerInputTranslator for movement checks.
|
||||
*/
|
||||
@Getter
|
||||
private boolean isRidingInFront;
|
||||
|
||||
private int lastAirSupply = getMaxAir();
|
||||
|
||||
/**
|
||||
* Determines if our position is currently out-of-sync with the Java server
|
||||
* due to our workaround for the void floor
|
||||
* <p>
|
||||
* Must be reset when dying, switching worlds, or being teleported out of the void
|
||||
*/
|
||||
@Getter @Setter
|
||||
private boolean voidPositionDesynched;
|
||||
|
||||
public SessionPlayerEntity(GeyserSession session) {
|
||||
super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null);
|
||||
|
||||
@ -88,10 +99,25 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
|
||||
@Override
|
||||
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
|
||||
if (voidPositionDesynched) {
|
||||
if (!isBelowVoidFloor()) {
|
||||
voidPositionDesynched = false; // No need to fix our offset; we've been moved
|
||||
}
|
||||
}
|
||||
super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround);
|
||||
session.getCollisionManager().updatePlayerBoundingBox(this.position.down(definition.offset()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
|
||||
if (voidPositionDesynched) {
|
||||
if (!isBelowVoidFloor()) {
|
||||
voidPositionDesynched = false; // No need to fix our offset; we've been moved
|
||||
}
|
||||
}
|
||||
super.moveAbsolute(position, yaw, pitch, headYaw, isOnGround, teleported);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPosition(Vector3f position) {
|
||||
if (valid) { // Don't update during session init
|
||||
@ -120,9 +146,7 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
// TODO: proper fix, BDS somehow does it? https://paste.gg/p/anonymous/3adfb7612f1540be80fa03a2281f93dc (BDS 1.20.13)
|
||||
if (!this.session.getGameMode().equals(GameMode.SPECTATOR)) {
|
||||
super.setFlags(entityMetadata);
|
||||
session.setSwimmingInWater((entityMetadata.getPrimitiveValue() & 0x10) == 0x10 && getFlag(EntityFlag.SPRINTING));
|
||||
}
|
||||
refreshSpeed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -150,7 +174,6 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
public void setPose(Pose pose) {
|
||||
super.setPose(pose);
|
||||
session.setPose(pose);
|
||||
refreshSpeed = true;
|
||||
}
|
||||
|
||||
public float getMaxHealth() {
|
||||
@ -167,7 +190,13 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
|
||||
@Override
|
||||
protected void setAirSupply(int amount) {
|
||||
if (amount == getMaxAir()) {
|
||||
// Seemingly required to be sent as of Bedrock 1.21. Otherwise, bubbles will appear as empty
|
||||
// Also, this changes how the air bubble graphics/sounds are presented. Breathing on means sound effects and
|
||||
// the bubbles visually pop
|
||||
setFlag(EntityFlag.BREATHING, amount >= this.lastAirSupply);
|
||||
this.lastAirSupply = amount;
|
||||
|
||||
if (amount == getMaxAir() && GameProtocol.isPre1_21_0(session)) {
|
||||
super.setAirSupply(0); // Hide the bubble counter from the UI for the player
|
||||
} else {
|
||||
super.setAirSupply(amount);
|
||||
@ -199,21 +228,6 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBedrockMetadata() {
|
||||
super.updateBedrockMetadata();
|
||||
if (refreshSpeed) {
|
||||
AttributeData speedAttribute = session.adjustSpeed();
|
||||
if (speedAttribute != null) {
|
||||
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||
attributesPacket.setRuntimeEntityId(geyserId);
|
||||
attributesPacket.setAttributes(Collections.singletonList(speedAttribute));
|
||||
session.sendUpstreamPacket(attributesPacket);
|
||||
}
|
||||
refreshSpeed = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateAttribute(Attribute javaAttribute, List<AttributeData> newAttributes) {
|
||||
if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_ATTACK_SPEED) {
|
||||
@ -226,17 +240,6 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
@Override
|
||||
protected AttributeData calculateAttribute(Attribute javaAttribute, GeyserAttributeType type) {
|
||||
AttributeData attributeData = super.calculateAttribute(javaAttribute, type);
|
||||
|
||||
if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_MOVEMENT_SPEED) {
|
||||
session.setOriginalSpeedAttribute(attributeData.getValue());
|
||||
AttributeData speedAttribute = session.adjustSpeed();
|
||||
if (speedAttribute != null) {
|
||||
// Overwrite the attribute with our own
|
||||
this.attributes.put(type, speedAttribute);
|
||||
return speedAttribute;
|
||||
}
|
||||
}
|
||||
|
||||
this.attributes.put(type, attributeData);
|
||||
return attributeData;
|
||||
}
|
||||
@ -244,11 +247,14 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
public void setLastDeathPosition(@Nullable GlobalPos pos) {
|
||||
if (pos != null) {
|
||||
dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_POS, pos.getPosition());
|
||||
dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_DIMENSION, DimensionUtils.javaToBedrock(pos.getDimension()));
|
||||
dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_DIMENSION, DimensionUtils.javaToBedrock(pos.getDimension().asString()));
|
||||
dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, true);
|
||||
} else {
|
||||
dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, false);
|
||||
}
|
||||
|
||||
// We're either respawning or switching worlds, either way, we are no longer desynched
|
||||
this.setVoidPositionDesynched(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -264,19 +270,13 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
super.setAbsorptionHearts(entityMetadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetMetadata() {
|
||||
// Reset all metadata to their default values
|
||||
// This is used when a player respawns
|
||||
this.flags.clear();
|
||||
this.initializeMetadata();
|
||||
super.resetMetadata();
|
||||
|
||||
// Reset air
|
||||
this.resetAir();
|
||||
|
||||
// Explicitly reset all metadata not handled by initializeMetadata
|
||||
setParrot(null, true);
|
||||
setParrot(null, false);
|
||||
|
||||
// Absorption is metadata in java edition
|
||||
attributes.remove(GeyserAttributeType.ABSORPTION);
|
||||
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||
@ -306,4 +306,48 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
public void resetAir() {
|
||||
this.setAirSupply(getMaxAir());
|
||||
}
|
||||
|
||||
private boolean isBelowVoidFloor() {
|
||||
return position.getY() < voidFloorPosition();
|
||||
}
|
||||
|
||||
public int voidFloorPosition() {
|
||||
// The void floor is offset about 40 blocks below the bottom of the world
|
||||
BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension();
|
||||
return bedrockDimension.minY() - 40;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles teleporting the player below or above the Bedrock void floor.
|
||||
* The Java server should never see this desync as we adjust the position that we send to it
|
||||
*
|
||||
* @param up in which direction to teleport - true to resync our position, or false to be
|
||||
* teleported below the void floor.
|
||||
*/
|
||||
public void teleportVoidFloorFix(boolean up) {
|
||||
// Safety to avoid double teleports
|
||||
if ((voidPositionDesynched && !up) || (!voidPositionDesynched && up)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Work around there being a floor at the bottom of the world and teleport the player below it
|
||||
// Moving from below to above the void floor works fine
|
||||
Vector3f newPosition = this.getPosition();
|
||||
if (up) {
|
||||
newPosition = newPosition.up(4f);
|
||||
voidPositionDesynched = false;
|
||||
} else {
|
||||
newPosition = newPosition.down(4f);
|
||||
voidPositionDesynched = true;
|
||||
}
|
||||
|
||||
this.setPositionManual(newPosition);
|
||||
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
|
||||
movePlayerPacket.setRuntimeEntityId(geyserId);
|
||||
movePlayerPacket.setPosition(newPosition);
|
||||
movePlayerPacket.setRotation(getBedrockRotation());
|
||||
movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT);
|
||||
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
|
||||
session.sendUpstreamPacketImmediately(movePlayerPacket);
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,10 @@ import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddPlayerPacket;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.block.property.Properties;
|
||||
import org.geysermc.geyser.level.block.type.BlockState;
|
||||
import org.geysermc.geyser.level.block.type.WallSkullBlock;
|
||||
import org.geysermc.geyser.level.physics.Direction;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.SkullCache;
|
||||
import org.geysermc.geyser.skin.SkullSkinManager;
|
||||
@ -137,20 +140,19 @@ public class SkullPlayerEntity extends PlayerEntity {
|
||||
float z = skull.getPosition().getZ() + .5f;
|
||||
float rotation;
|
||||
|
||||
int blockState = skull.getBlockState();
|
||||
byte floorRotation = BlockStateValues.getSkullRotation(blockState);
|
||||
if (floorRotation == -1) {
|
||||
// Wall skull
|
||||
BlockState blockState = skull.getBlockState();
|
||||
if (blockState.block() instanceof WallSkullBlock) {
|
||||
y += 0.25f;
|
||||
rotation = BlockStateValues.getSkullWallDirections().get(blockState);
|
||||
switch ((int) rotation) {
|
||||
case 180 -> z += 0.24f; // North
|
||||
case 0 -> z -= 0.24f; // South
|
||||
case 90 -> x += 0.24f; // West
|
||||
case 270 -> x -= 0.24f; // East
|
||||
Direction direction = blockState.getValue(Properties.HORIZONTAL_FACING);
|
||||
rotation = WallSkullBlock.getDegrees(direction);
|
||||
switch (direction) {
|
||||
case NORTH -> z += 0.24f;
|
||||
case SOUTH -> z -= 0.24f;
|
||||
case WEST -> x += 0.24f;
|
||||
case EAST -> x -= 0.24f;
|
||||
}
|
||||
} else {
|
||||
rotation = (180f + (floorRotation * 22.5f)) % 360;
|
||||
rotation = (180f + blockState.getValue(Properties.ROTATION_16, 0) * 22.5f) % 360;
|
||||
}
|
||||
|
||||
moveAbsolute(Vector3f.from(x, y, z), rotation, 0, rotation, true, true);
|
||||
|
Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren