diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..c32bad0c8
--- /dev/null
+++ b/.editorconfig
@@ -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
diff --git a/.github/workflows/build-remote.yml b/.github/workflows/build-remote.yml
index d49920785..7cb89cc61 100644
--- a/.github/workflows/build-remote.yml
+++ b/.github/workflows/build-remote.yml
@@ -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
\ No newline at end of file
+ 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
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7ec013dc2..59aa89086 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -16,108 +16,59 @@ on:
- 'LICENSE'
- 'Jenkinsfile '
- 'README.md'
- - 'licenseheader.txt'
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
-
- - 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
+ BUILD_NUMBER: ${{ steps.release-info.outputs.curentRelease }}
- - 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 +82,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' }}
diff --git a/.github/workflows/dispatch-preview.yml b/.github/workflows/dispatch-preview.yml
new file mode 100644
index 000000000..83df08e37
--- /dev/null
+++ b/.github/workflows/dispatch-preview.yml
@@ -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 }}
\ No newline at end of file
diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml
deleted file mode 100644
index 1268f0674..000000000
--- a/.github/workflows/preview.yml
+++ /dev/null
@@ -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/
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index bc5e57b6b..6167bb18e 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -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 }}
\ No newline at end of file
+ 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 }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index a44afd242..aff61aa60 100644
--- a/.gitignore
+++ b/.gitignore
@@ -249,6 +249,8 @@ locales/
/packs/
/dump.json
/saved-refresh-tokens.json
+/saved-auth-chains.json
/custom_mappings/
/languages/
-/custom-skulls.yml
\ No newline at end of file
+/custom-skulls.yml
+/permissions.yml
diff --git a/.idea/copyright/Geyser.xml b/.idea/copyright/Geyser.xml
index c6b553aaf..758c31cbd 100644
--- a/.idea/copyright/Geyser.xml
+++ b/.idea/copyright/Geyser.xml
@@ -1,6 +1,7 @@
-
+
+
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index bde252698..ba3a723ff 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License
-Copyright (c) 2019-2023 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
diff --git a/README.md b/README.md
index 9257af9ac..fb93a8808 100644
--- a/README.md
+++ b/README.md
@@ -14,16 +14,15 @@ 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
+## Supported Versions
+Geyser is currently supporting Minecraft Bedrock 1.20.80 - 1.21.21 and Minecraft Java 1.21/1.21.1. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
## Setting Up
-Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
-
-[![YouTube Video](https://img.youtube.com/vi/U7dZZ8w7Gi4/0.jpg)](https://www.youtube.com/watch?v=U7dZZ8w7Gi4)
+Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser.
## Links:
- Website: https://geysermc.org
-- Docs: https://wiki.geysermc.org/geyser/
+- Docs: https://geysermc.org/wiki/geyser/
- Download: https://geysermc.org/download
- Discord: https://discord.gg/geysermc
- Donate: https://opencollective.com/geysermc
@@ -34,7 +33,7 @@ Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Ge
- Some Entity Flags
## What can't be fixed
-There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://wiki.geysermc.org/geyser/current-limitations/) page.
+There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://geysermc.org/wiki/geyser/current-limitations/) page.
## Compiling
1. Clone the repo to your computer
@@ -42,12 +41,12 @@ There are a few things Geyser is unable to support due to various differences be
3. Run `gradlew build` and locate to `bootstrap/build` folder.
## Contributing
-Any contributions are appreciated. Please feel free to reach out to us on [Discord](http://discord.geysermc.org/) if
+Any contributions are appreciated. Please feel free to reach out to us on [Discord](https://discord.gg/geysermc) if
you're interested in helping out with Geyser.
## Libraries Used:
- [Adventure Text Library](https://github.com/KyoriPowered/adventure)
-- [NukkitX Bedrock Protocol Library](https://github.com/NukkitX/Protocol)
-- [Steveice10's Java Protocol Library](https://github.com/Steveice10/MCProtocolLib)
+- [CloudburstMC Bedrock Protocol Library](https://github.com/CloudburstMC/Protocol)
+- [GeyserMC's Java Protocol Library](https://github.com/GeyserMC/MCProtocolLib)
- [TerminalConsoleAppender](https://github.com/Minecrell/TerminalConsoleAppender)
- [Simple Logging Facade for Java (slf4j)](https://github.com/qos-ch/slf4j)
diff --git a/ap/build.gradle.kts b/ap/build.gradle.kts
index e69de29bb..6c456c21b 100644
--- a/ap/build.gradle.kts
+++ b/ap/build.gradle.kts
@@ -0,0 +1,3 @@
+plugins {
+ id("geyser.base-conventions")
+}
diff --git a/api/build.gradle.kts b/api/build.gradle.kts
index bd54a9ce4..eac02ebeb 100644
--- a/api/build.gradle.kts
+++ b/api/build.gradle.kts
@@ -1,8 +1,24 @@
plugins {
+ // Allow blossom to mark sources root of templates
+ idea
id("geyser.publish-conventions")
+ alias(libs.plugins.blossom)
}
dependencies {
api(libs.base.api)
api(libs.math)
-}
\ No newline at end of file
+}
+
+version = property("version")!!
+val apiVersion = (version as String).removeSuffix("-SNAPSHOT")
+
+sourceSets {
+ main {
+ blossom {
+ javaSources {
+ property("version", apiVersion)
+ }
+ }
+ }
+}
diff --git a/api/src/main/java-templates/org/geysermc/geyser/api/BuildData.java b/api/src/main/java-templates/org/geysermc/geyser/api/BuildData.java
new file mode 100644
index 000000000..f9a580e7b
--- /dev/null
+++ b/api/src/main/java-templates/org/geysermc/geyser/api/BuildData.java
@@ -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.api;
+
+import org.geysermc.api.util.ApiVersion;
+
+/**
+ * Not a public API. For internal use only. May change without notice.
+ * This class is processed before compilation to insert build properties.
+ */
+class BuildData {
+ static final String VERSION = "{{ version }}";
+ static final ApiVersion API_VERSION;
+
+ static {
+ String[] parts = VERSION.split("\\.");
+ if (parts.length != 3) {
+ throw new RuntimeException("Invalid api version: " + VERSION);
+ }
+
+ try {
+ int human = Integer.parseInt(parts[0]);
+ int major = Integer.parseInt(parts[1]);
+ int minor = Integer.parseInt(parts[2]);
+ API_VERSION = new ApiVersion(human, major, minor);
+ } catch (Exception e) {
+ throw new RuntimeException("Invalid api version: " + VERSION, e);
+ }
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java b/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java
index a9327d0db..5c20d06e1 100644
--- a/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java
+++ b/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java
@@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.Geyser;
import org.geysermc.api.GeyserApiBase;
+import org.geysermc.api.util.ApiVersion;
import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.EventBus;
@@ -169,4 +170,14 @@ public interface GeyserApi extends GeyserApiBase {
static GeyserApi api() {
return Geyser.api(GeyserApi.class);
}
+
+ /**
+ * Returns the {@link ApiVersion} representing the current Geyser api version.
+ * See the Geyser version outline)
+ *
+ * @return the current geyser api version
+ */
+ default ApiVersion geyserApiVersion() {
+ return BuildData.API_VERSION;
+ }
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraData.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraData.java
index 2f715fa1e..f208879d1 100644
--- a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraData.java
+++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraData.java
@@ -145,4 +145,36 @@ public interface CameraData {
* @return whether the camera is currently locked
*/
boolean isCameraLocked();
-}
\ No newline at end of file
+
+ /**
+ * 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.
+ *
+ * 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 hiddenElements();
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/GuiElement.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/GuiElement.java
new file mode 100644
index 000000000..4d3653648
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/GuiElement.java
@@ -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;
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java b/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java
index f7da4b932..0dd0d3b33 100644
--- a/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java
+++ b/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java
@@ -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();
diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java
index 2f1f2b24d..29922ae1e 100644
--- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java
+++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java
@@ -28,7 +28,9 @@ package org.geysermc.geyser.api.command;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.connection.GeyserConnection;
+import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
import org.geysermc.geyser.api.extension.Extension;
+import org.geysermc.geyser.api.util.TriState;
import java.util.Collections;
import java.util.List;
@@ -58,15 +60,15 @@ public interface Command {
* Gets the permission node associated with
* this command.
*
- * @return the permission node for this command
+ * @return the permission node for this command if defined, otherwise an empty string
*/
@NonNull
String permission();
/**
- * Gets the aliases for this command.
+ * Gets the aliases for this command, as an unmodifiable list
*
- * @return the aliases for this command
+ * @return the aliases for this command as an unmodifiable list
*/
@NonNull
List aliases();
@@ -75,35 +77,39 @@ public interface Command {
* Gets if this command is designed to be used only by server operators.
*
* @return if this command is designated to be used only by server operators.
+ * @deprecated this method is not guaranteed to provide meaningful or expected results.
*/
- boolean isSuggestedOpOnly();
-
- /**
- * Gets if this command is executable on console.
- *
- * @return if this command is executable on console
- */
- boolean isExecutableOnConsole();
-
- /**
- * Gets the subcommands associated with this
- * command. Mainly used within the Geyser Standalone
- * GUI to know what subcommands are supported.
- *
- * @return the subcommands associated with this command
- */
- @NonNull
- default List subCommands() {
- return Collections.emptyList();
+ @Deprecated(forRemoval = true)
+ default boolean isSuggestedOpOnly() {
+ return false;
}
/**
- * Used to send a deny message to Java players if this command can only be used by Bedrock players.
- *
- * @return true if this command can only be used by Bedrock players.
+ * @return true if this command is executable on console
+ * @deprecated use {@link #isPlayerOnly()} instead (inverted)
*/
- default boolean isBedrockOnly() {
- return false;
+ @Deprecated(forRemoval = true)
+ default boolean isExecutableOnConsole() {
+ return !isPlayerOnly();
+ }
+
+ /**
+ * @return true if this command can only be used by players
+ */
+ boolean isPlayerOnly();
+
+ /**
+ * @return true if this command can only be used by Bedrock players
+ */
+ boolean isBedrockOnly();
+
+ /**
+ * @deprecated this method will always return an empty immutable list
+ */
+ @Deprecated(forRemoval = true)
+ @NonNull
+ default List subCommands() {
+ return Collections.emptyList();
}
/**
@@ -128,7 +134,7 @@ public interface Command {
* is an instance of this source.
*
* @param sourceType the source type
- * @return the builder
+ * @return this builder
*/
Builder source(@NonNull Class extends T> sourceType);
@@ -136,7 +142,7 @@ public interface Command {
* Sets the command name.
*
* @param name the command name
- * @return the builder
+ * @return this builder
*/
Builder name(@NonNull String name);
@@ -144,23 +150,40 @@ public interface Command {
* Sets the command description.
*
* @param description the command description
- * @return the builder
+ * @return this builder
*/
Builder description(@NonNull String description);
/**
- * Sets the permission node.
+ * Sets the permission node required to run this command.
+ * It will not be registered with any permission registries, such as an underlying server,
+ * or a permissions Extension (unlike {@link #permission(String, TriState)}).
*
* @param permission the permission node
- * @return the builder
+ * @return this builder
*/
Builder permission(@NonNull String permission);
+ /**
+ * Sets the permission node and its default value. The usage of the default value is platform dependant
+ * and may or may not be used. For example, it may be registered to an underlying server.
+ *
+ * Extensions may instead listen for {@link GeyserRegisterPermissionsEvent} to register permissions,
+ * especially if the same permission is required by multiple commands. Also see this event for TriState meanings.
+ *
+ * @param permission the permission node
+ * @param defaultValue the node's default value
+ * @return this builder
+ * @deprecated this method is experimental and may be removed in the future
+ */
+ @Deprecated
+ Builder permission(@NonNull String permission, @NonNull TriState defaultValue);
+
/**
* Sets the aliases.
*
* @param aliases the aliases
- * @return the builder
+ * @return this builder
*/
Builder aliases(@NonNull List aliases);
@@ -168,46 +191,62 @@ public interface Command {
* Sets if this command is designed to be used only by server operators.
*
* @param suggestedOpOnly if this command is designed to be used only by server operators
- * @return the builder
+ * @return this builder
+ * @deprecated this method is not guaranteed to produce meaningful or expected results
*/
+ @Deprecated(forRemoval = true)
Builder suggestedOpOnly(boolean suggestedOpOnly);
/**
* Sets if this command is executable on console.
*
* @param executableOnConsole if this command is executable on console
- * @return the builder
+ * @return this builder
+ * @deprecated use {@link #isPlayerOnly()} instead (inverted)
*/
+ @Deprecated(forRemoval = true)
Builder executableOnConsole(boolean executableOnConsole);
+ /**
+ * Sets if this command can only be executed by players.
+ *
+ * @param playerOnly if this command is player only
+ * @return this builder
+ */
+ Builder playerOnly(boolean playerOnly);
+
+ /**
+ * Sets if this command can only be executed by bedrock players.
+ *
+ * @param bedrockOnly if this command is bedrock only
+ * @return this builder
+ */
+ Builder bedrockOnly(boolean bedrockOnly);
+
/**
* Sets the subcommands.
*
* @param subCommands the subcommands
- * @return the builder
+ * @return this builder
+ * @deprecated this method has no effect
*/
- Builder subCommands(@NonNull List subCommands);
-
- /**
- * Sets if this command is bedrock only.
- *
- * @param bedrockOnly if this command is bedrock only
- * @return the builder
- */
- Builder bedrockOnly(boolean bedrockOnly);
+ @Deprecated(forRemoval = true)
+ default Builder subCommands(@NonNull List subCommands) {
+ return this;
+ }
/**
* Sets the {@link CommandExecutor} for this command.
*
* @param executor the command executor
- * @return the builder
+ * @return this builder
*/
Builder executor(@NonNull CommandExecutor executor);
/**
* Builds the command.
*
- * @return the command
+ * @return a new command from this builder
*/
@NonNull
Command build();
diff --git a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java
index 45276e2c4..c1453f579 100644
--- a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java
+++ b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java
@@ -26,6 +26,10 @@
package org.geysermc.geyser.api.command;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.geyser.api.connection.GeyserConnection;
+
+import java.util.UUID;
/**
* Represents an instance capable of sending commands.
@@ -64,6 +68,17 @@ public interface CommandSource {
*/
boolean isConsole();
+ /**
+ * @return a Java UUID if this source represents a player, otherwise null
+ */
+ @Nullable UUID playerUuid();
+
+ /**
+ * @return a GeyserConnection if this source represents a Bedrock player that is connected
+ * to this Geyser instance, otherwise null
+ */
+ @Nullable GeyserConnection connection();
+
/**
* Returns the locale of the command source.
*
diff --git a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java
index 9bda4f903..0a580f975 100644
--- a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java
+++ b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java
@@ -60,6 +60,16 @@ public interface GeyserConnection extends Connection, CommandSource {
*/
@NonNull EntityData entities();
+ /**
+ * Returns the current ping of the connection.
+ */
+ int ping();
+
+ /**
+ * Closes the currently open form on the client.
+ */
+ void closeForm();
+
/**
* @param javaId the Java entity ID to look up.
* @return a {@link GeyserEntity} if present in this connection's entity tracker.
diff --git a/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java b/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java
index 90b3fc821..48c717089 100644
--- a/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java
+++ b/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java
@@ -81,4 +81,10 @@ public interface EntityData {
* @return whether the movement is locked
*/
boolean isMovementLocked();
+
+ /**
+ * Sends a request to the Java server to switch the items in the main and offhand.
+ * There is no guarantee of the server accepting the request.
+ */
+ void switchHands();
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoginEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoginEvent.java
index e45a6162c..9d368031b 100644
--- a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoginEvent.java
+++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoginEvent.java
@@ -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 {
@@ -117,9 +117,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;
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java
new file mode 100644
index 000000000..f22241e41
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java
@@ -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.
+ *
+ * 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.
+ *
+ * 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));
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionRequestEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionRequestEvent.java
index c7b8284ef..c11f837d8 100644
--- a/api/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionRequestEvent.java
+++ b/api/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionRequestEvent.java
@@ -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;
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java
index 994373752..d136202bd 100644
--- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java
+++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java
@@ -50,7 +50,7 @@ public interface GeyserDefineCommandsEvent extends Event {
/**
* Gets all the registered built-in {@link Command}s.
*
- * @return all the registered built-in commands
+ * @return all the registered built-in commands as an unmodifiable map
*/
@NonNull
Map commands();
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java
new file mode 100644
index 000000000..43ebc2c50
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2019-2023 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.lifecycle;
+
+import org.geysermc.event.Event;
+import org.geysermc.event.PostOrder;
+import org.geysermc.geyser.api.permission.PermissionChecker;
+
+/**
+ * Fired by any permission manager implementations that wish to add support for custom permission checking.
+ * This event is not guaranteed to be fired - it is currently only fired on Geyser-Standalone and ViaProxy.
+ *
+ * Subscribing to this event with an earlier {@link PostOrder} and registering a {@link PermissionChecker}
+ * will result in that checker having a higher priority than others.
+ */
+public interface GeyserRegisterPermissionCheckersEvent extends Event {
+
+ void register(PermissionChecker checker);
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java
new file mode 100644
index 000000000..4f06c4e5f
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019-2023 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.lifecycle;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.event.Event;
+import org.geysermc.geyser.api.util.TriState;
+
+/**
+ * Fired by anything that wishes to gather permission nodes and defaults.
+ *
+ * This event is not guaranteed to be fired, as certain Geyser platforms do not have a native permission system.
+ * It can be expected to fire on Geyser-Spigot, Geyser-NeoForge, Geyser-Standalone, and Geyser-ViaProxy
+ * It may be fired by a 3rd party regardless of the platform.
+ */
+public interface GeyserRegisterPermissionsEvent extends Event {
+
+ /**
+ * Registers a permission node and its default value with the firer.
+ * {@link TriState#TRUE} corresponds to all players having the permission by default.
+ * {@link TriState#NOT_SET} corresponds to only server operators having the permission by default (if such a concept exists on the platform).
+ * {@link TriState#FALSE} corresponds to no players having the permission by default.
+ *
+ * @param permission the permission node to register
+ * @param defaultValue the default value of the node
+ */
+ void register(@NonNull String permission, @NonNull TriState defaultValue);
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java b/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java
index 993bdee44..1eacfea9a 100644
--- a/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java
+++ b/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java
@@ -107,6 +107,15 @@ public interface Extension extends EventRegistrar {
return this.extensionLoader().description(this);
}
+ /**
+ * @return the root command that all of this extension's commands will stem from.
+ * By default, this is the extension's id.
+ */
+ @NonNull
+ default String rootCommand() {
+ return this.description().id();
+ }
+
/**
* Gets the extension's logger
*
diff --git a/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java
index 2df3ee815..25daf450f 100644
--- a/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java
+++ b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java
@@ -59,33 +59,46 @@ public interface ExtensionDescription {
String main();
/**
- * Gets the extension's major api version
+ * Represents the human api version that the extension requires.
+ * See the Geyser version outline)
+ * for more details on the Geyser API version.
*
- * @return the extension's major api version
+ * @return the extension's requested human api version
+ */
+ int humanApiVersion();
+
+ /**
+ * Represents the major api version that the extension requires.
+ * See the Geyser version outline)
+ * for more details on the Geyser API version.
+ *
+ * @return the extension's requested major api version
*/
int majorApiVersion();
/**
- * Gets the extension's minor api version
+ * Represents the minor api version that the extension requires.
+ * See the Geyser version outline)
+ * for more details on the Geyser API version.
*
- * @return the extension's minor api version
+ * @return the extension's requested minor api version
*/
int minorApiVersion();
/**
- * Gets the extension's patch api version
- *
- * @return the extension's patch api version
+ * No longer in use. Geyser is now using an adaption of the romantic versioning scheme.
+ * See here for details.
*/
- int patchApiVersion();
+ @Deprecated(forRemoval = true)
+ default int patchApiVersion() {
+ return minorApiVersion();
+ }
/**
- * Gets the extension's api version.
- *
- * @return the extension's api version
+ * Returns the extension's requested Geyser Api version.
*/
default String apiVersion() {
- return majorApiVersion() + "." + minorApiVersion() + "." + patchApiVersion();
+ return humanApiVersion() + "." + majorApiVersion() + "." + minorApiVersion();
}
/**
diff --git a/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java
new file mode 100644
index 000000000..c0d4af2f4
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2019-2023 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.permission;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.api.command.CommandSource;
+import org.geysermc.geyser.api.util.TriState;
+
+/**
+ * Something capable of checking if a {@link CommandSource} has a permission
+ */
+@FunctionalInterface
+public interface PermissionChecker {
+
+ /**
+ * Checks if the given source has a permission
+ *
+ * @param source the {@link CommandSource} whose permissions should be queried
+ * @param permission the permission node to check
+ * @return a {@link TriState} as the value of the node. {@link TriState#NOT_SET} generally means that the permission
+ * node itself was not found, and the source does not have such permission.
+ * {@link TriState#TRUE} and {@link TriState#FALSE} represent explicitly set values.
+ */
+ @NonNull
+ TriState hasPermission(@NonNull CommandSource source, @NonNull String permission);
+}
diff --git a/core/src/main/java/org/geysermc/geyser/util/collection/package-info.java b/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java
similarity index 66%
rename from core/src/main/java/org/geysermc/geyser/util/collection/package-info.java
rename to api/src/main/java/org/geysermc/geyser/api/skin/Cape.java
index 46fa5df11..1e7341ae4 100644
--- a/core/src/main/java/org/geysermc/geyser/util/collection/package-info.java
+++ b/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java
@@ -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.
- *
- * 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;
\ No newline at end of file
+public record Cape(String textureUrl, String capeId, byte[] capeData, boolean failed) {
+ public Cape(String textureUrl, String capeId, byte[] capeData) {
+ this(textureUrl, capeId, capeData, false);
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java b/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java
new file mode 100644
index 000000000..9b39ddfe8
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java
@@ -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);
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java b/api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java
new file mode 100644
index 000000000..9de4a3534
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java
@@ -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) {
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java b/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java
new file mode 100644
index 000000000..5b40d2022
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java
@@ -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" : "") + "\"}}", "");
+ }
+}
diff --git a/bootstrap/bungeecord/base/build.gradle.kts b/bootstrap/bungeecord/base/build.gradle.kts
index c4f1ed07c..e31b795d6 100644
--- a/bootstrap/bungeecord/base/build.gradle.kts
+++ b/bootstrap/bungeecord/base/build.gradle.kts
@@ -1,9 +1,12 @@
plugins {
- id("geyser.publish-conventions")
+ id("geyser.platform-conventions")
+ id("geyser.modrinth-uploading-conventions")
}
dependencies {
api(projects.core)
+
+ implementation(libs.cloud.bungee)
implementation(libs.adventure.text.serializer.bungeecord)
compileOnlyApi(libs.bungeecord.proxy)
@@ -14,6 +17,8 @@ platformRelocate("net.md_5.bungee.jni")
platformRelocate("com.fasterxml.jackson")
platformRelocate("io.netty.channel.kqueue") // This is not used because relocating breaks natives, but we must include it or else we get ClassDefNotFound
platformRelocate("net.kyori")
+platformRelocate("org.incendo")
+platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
platformRelocate("org.yaml") // Broken as of 1.20
// These dependencies are already present on the platform
@@ -36,3 +41,8 @@ tasks.withType {
exclude(dependency("io.netty:netty-resolver-dns:.*"))
}
}
+
+modrinth {
+ uploadFile.set(tasks.getByPath("shadowJar"))
+ loaders.add("bungeecord")
+}
diff --git a/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeLogger.java b/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeLogger.java
index daeb20102..e8cf7ee39 100644
--- a/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeLogger.java
+++ b/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeLogger.java
@@ -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);
diff --git a/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java b/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java
index 3110df8d3..a79430195 100644
--- a/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java
+++ b/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java
@@ -36,6 +36,7 @@ import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.protocol.ProtocolConstants;
import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.ping.GeyserPingInfo;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
@@ -43,6 +44,7 @@ import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
@AllArgsConstructor
public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, Listener {
@@ -59,7 +61,17 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
future.complete(event);
}
}));
- ProxyPingEvent event = future.join();
+
+ ProxyPingEvent event;
+
+ try {
+ event = future.get(100, TimeUnit.MILLISECONDS);
+ } catch (Throwable cause) {
+ String address = GeyserImpl.getInstance().getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : "";
+ GeyserImpl.getInstance().getLogger().error("Failed to get ping information for " + address, cause);
+ return null;
+ }
+
ServerPing response = event.getResponse();
return new GeyserPingInfo(
response.getDescriptionComponent().toLegacyText(),
diff --git a/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java
index 4ec167143..6418b1076 100644
--- a/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java
+++ b/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java
@@ -27,6 +27,7 @@ package org.geysermc.geyser.platform.bungeecord;
import io.netty.channel.Channel;
import net.md_5.bungee.BungeeCord;
+import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.protocol.ProtocolConstants;
@@ -35,18 +36,21 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.floodgate.core.skin.SkinApplier;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
-import org.geysermc.geyser.api.command.Command;
-import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.util.PlatformType;
-import org.geysermc.geyser.command.GeyserCommandManager;
+import org.geysermc.geyser.command.CommandRegistry;
+import org.geysermc.geyser.command.CommandSourceConverter;
+import org.geysermc.geyser.command.GeyserCommandSource;
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.bungeecord.command.GeyserBungeeCommandExecutor;
+import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
+import org.incendo.cloud.CommandManager;
+import org.incendo.cloud.bungee.BungeeCommandManager;
+import org.incendo.cloud.execution.ExecutionCoordinator;
import java.io.File;
import java.io.IOException;
@@ -56,20 +60,17 @@ import java.net.SocketAddress;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
-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 CommandRegistry commandRegistry;
private GeyserBungeeConfiguration geyserConfig;
private GeyserBungeeInjector geyserInjector;
- private GeyserBungeeLogger geyserLogger;
+ private final GeyserBungeeLogger geyserLogger = new GeyserBungeeLogger(getLogger());
private IGeyserPingPassthrough geyserBungeePingPassthrough;
-
private GeyserImpl geyser;
@Override
@@ -84,28 +85,49 @@ 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, null);
this.geyserInjector = new GeyserBungeeInjector(this);
+
+ // Registration of listeners occurs only once
+ this.getProxy().getPluginManager().registerListener(this, new GeyserBungeeUpdateListener());
}
@Override
public void onEnable() {
+ if (geyser == null) {
+ return; // Config did not load properly!
+ }
+
+ // After Geyser initialize for parity with other platforms.
+ var sourceConverter = new CommandSourceConverter<>(
+ CommandSender.class,
+ id -> getProxy().getPlayer(id),
+ () -> getProxy().getConsole(),
+ BungeeCommandSource::new
+ );
+ CommandManager cloud = new BungeeCommandManager<>(
+ this,
+ ExecutionCoordinator.simpleCoordinator(),
+ sourceConverter
+ );
+ this.commandRegistry = new CommandRegistry(geyser, cloud, false); // applying root permission would be a breaking change because we can't register permission defaults
+
// Big hack - Bungee does not provide us an event to listen to, so schedule a repeating
// task that waits for a field to be filled which is set after the plugin enable
// process is complete
@@ -145,11 +167,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
}
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
- } else {
- // For consistency with other platforms - create command manager before GeyserImpl#start()
- // This ensures the command events are called before the item/block ones are
- this.geyserCommandManager = new GeyserCommandManager(geyser);
- this.geyserCommandManager.init();
}
// Force-disable query if enabled, or else Geyser won't enable
@@ -192,16 +209,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
}
this.geyserInjector.initializeLocalChannel(this);
-
- this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands()));
- for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
- Map commands = entry.getValue();
- if (commands.isEmpty()) {
- continue;
- }
-
- this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(entry.getKey().description().id(), this.geyser, commands));
- }
}
@Override
@@ -237,8 +244,8 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
}
@Override
- public GeyserCommandManager getGeyserCommandManager() {
- return this.geyserCommandManager;
+ public CommandRegistry getCommandRegistry() {
+ return this.commandRegistry;
}
@Override
@@ -303,7 +310,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;
}
diff --git a/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java b/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java
index c68839b20..0a89b5421 100644
--- a/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java
+++ b/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java
@@ -29,8 +29,8 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
-import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.Permissions;
import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource;
import org.geysermc.geyser.util.VersionCheckUtils;
@@ -40,7 +40,7 @@ public final class GeyserBungeeUpdateListener implements Listener {
public void onPlayerJoin(final PostLoginEvent event) {
if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
final ProxiedPlayer player = event.getPlayer();
- if (player.hasPermission(Constants.UPDATE_PERMISSION)) {
+ if (player.hasPermission(Permissions.CHECK_UPDATE)) {
VersionCheckUtils.checkForGeyserUpdate(() -> new BungeeCommandSource(player));
}
}
diff --git a/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java b/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java
index e3099f170..10ccc5bac 100644
--- a/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java
+++ b/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java
@@ -27,19 +27,22 @@ package org.geysermc.geyser.platform.bungeecord.command;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
+import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.Locale;
+import java.util.UUID;
public class BungeeCommandSource implements GeyserCommandSource {
- private final net.md_5.bungee.api.CommandSender handle;
+ private final CommandSender handle;
- public BungeeCommandSource(net.md_5.bungee.api.CommandSender handle) {
+ public BungeeCommandSource(CommandSender handle) {
this.handle = handle;
// Ensure even Java players' languages are loaded
GeyserLocale.loadGeyserLocale(this.locale());
@@ -72,12 +75,20 @@ public class BungeeCommandSource implements GeyserCommandSource {
return !(handle instanceof ProxiedPlayer);
}
+ @Override
+ public @Nullable UUID playerUuid() {
+ if (handle instanceof ProxiedPlayer player) {
+ return player.getUniqueId();
+ }
+ return null;
+ }
+
@Override
public String locale() {
if (handle instanceof ProxiedPlayer player) {
Locale locale = player.getLocale();
if (locale != null) {
- // Locale can be null early on in the conneciton
+ // Locale can be null early on in the connection
return GeyserLocale.formatLocale(locale.getLanguage() + "_" + locale.getCountry());
}
}
@@ -86,6 +97,12 @@ public class BungeeCommandSource implements GeyserCommandSource {
@Override
public boolean hasPermission(String permission) {
- return handle.hasPermission(permission);
+ // Handle blank permissions ourselves, as bungeecord only handles empty ones
+ return permission.isBlank() || handle.hasPermission(permission);
+ }
+
+ @Override
+ public Object handle() {
+ return handle;
}
}
diff --git a/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java
deleted file mode 100644
index 2d02c9950..000000000
--- a/bootstrap/bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (c) 2019-2022 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.bungeecord.command;
-
-import net.md_5.bungee.api.ChatColor;
-import net.md_5.bungee.api.CommandSender;
-import net.md_5.bungee.api.plugin.Command;
-import net.md_5.bungee.api.plugin.TabExecutor;
-import org.geysermc.geyser.GeyserImpl;
-import org.geysermc.geyser.command.GeyserCommand;
-import org.geysermc.geyser.command.GeyserCommandExecutor;
-import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.text.GeyserLocale;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Map;
-
-public class GeyserBungeeCommandExecutor extends Command implements TabExecutor {
- private final GeyserCommandExecutor commandExecutor;
-
- public GeyserBungeeCommandExecutor(String name, GeyserImpl geyser, Map commands) {
- super(name);
-
- this.commandExecutor = new GeyserCommandExecutor(geyser, commands);
- }
-
- @Override
- public void execute(CommandSender sender, String[] args) {
- BungeeCommandSource commandSender = new BungeeCommandSource(sender);
- GeyserSession session = this.commandExecutor.getGeyserSession(commandSender);
-
- if (args.length > 0) {
- GeyserCommand command = this.commandExecutor.getCommand(args[0]);
- if (command != null) {
- if (!sender.hasPermission(command.permission())) {
- String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale());
-
- commandSender.sendMessage(ChatColor.RED + message);
- return;
- }
- if (command.isBedrockOnly() && session == null) {
- String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale());
-
- commandSender.sendMessage(ChatColor.RED + message);
- return;
- }
- command.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
- } else {
- String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale());
- commandSender.sendMessage(ChatColor.RED + message);
- }
- } else {
- this.commandExecutor.getCommand("help").execute(session, commandSender, new String[0]);
- }
- }
-
- @Override
- public Iterable onTabComplete(CommandSender sender, String[] args) {
- if (args.length == 1) {
- return commandExecutor.tabComplete(new BungeeCommandSource(sender));
- } else {
- return Collections.emptyList();
- }
- }
-}
diff --git a/bootstrap/bungeecord/isolated/build.gradle.kts b/bootstrap/bungeecord/isolated/build.gradle.kts
index 6516ba401..f8200557e 100644
--- a/bootstrap/bungeecord/isolated/build.gradle.kts
+++ b/bootstrap/bungeecord/isolated/build.gradle.kts
@@ -1,5 +1,6 @@
plugins {
- java
+ id("geyser.base-conventions")
+ application
}
dependencies {
@@ -32,4 +33,4 @@ tasks {
into("bundled/")
}
}
-}
\ No newline at end of file
+}
diff --git a/bootstrap/mod/build.gradle.kts b/bootstrap/mod/build.gradle.kts
index 7651a2df2..c43f123ec 100644
--- a/bootstrap/mod/build.gradle.kts
+++ b/bootstrap/mod/build.gradle.kts
@@ -1,3 +1,7 @@
+plugins {
+ id("geyser.modded-conventions")
+}
+
architectury {
common("neoforge", "fabric")
}
@@ -6,10 +10,18 @@ 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)
+ compileOnly(libs.mixinextras)
// Only here to suppress "unknown enum constant EnvType.CLIENT" warnings. DO NOT USE!
compileOnly(libs.fabric.loader)
-}
\ No newline at end of file
+}
diff --git a/bootstrap/mod/fabric/build.gradle.kts b/bootstrap/mod/fabric/build.gradle.kts
index 3880db895..e6e172e2d 100644
--- a/bootstrap/mod/fabric/build.gradle.kts
+++ b/bootstrap/mod/fabric/build.gradle.kts
@@ -1,5 +1,6 @@
plugins {
- application
+ id("geyser.modded-conventions")
+ id("geyser.modrinth-uploading-conventions")
}
architectury {
@@ -25,10 +26,7 @@ dependencies {
shadow(libs.protocol.connection) { isTransitive = false }
shadow(libs.protocol.common) { isTransitive = false }
shadow(libs.protocol.codec) { isTransitive = false }
- shadow(libs.mcauthlib) { isTransitive = false }
shadow(libs.raknet) { isTransitive = false }
-
- // Consequences of shading + relocating mcauthlib: shadow/relocate mcpl!
shadow(libs.mcprotocollib) { isTransitive = false }
// Since we also relocate cloudburst protocol: shade erosion common
@@ -37,13 +35,12 @@ dependencies {
// Let's shade in our own api/common module
shadow(projects.api) { isTransitive = false }
- // Permissions
- modImplementation(libs.fabric.permissions)
- include(libs.fabric.permissions)
+ modImplementation(libs.cloud.fabric)
+ include(libs.cloud.fabric)
}
-application {
- mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain")
+tasks.withType {
+ manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.fabric.GeyserFabricMain"
}
relocate("org.cloudburstmc.netty")
@@ -62,7 +59,8 @@ tasks {
modrinth {
loaders.add("fabric")
+ uploadFile.set(tasks.getByPath("remapModrinthJar"))
dependencies {
required.project("fabric-api")
}
-}
\ No newline at end of file
+}
diff --git a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java
index c363ade8f..149246d59 100644
--- a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java
+++ b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java
@@ -25,7 +25,6 @@
package org.geysermc.geyser.platform.fabric;
-import me.lucko.fabric.api.permissions.v0.Permissions;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
@@ -34,9 +33,16 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.world.entity.player.Player;
-import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.command.CommandRegistry;
+import org.geysermc.geyser.command.CommandSourceConverter;
+import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
+import org.geysermc.geyser.platform.mod.command.ModCommandSource;
+import org.incendo.cloud.CommandManager;
+import org.incendo.cloud.execution.ExecutionCoordinator;
+import org.incendo.cloud.fabric.FabricServerCommandManager;
public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInitializer {
@@ -70,20 +76,23 @@ public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInit
ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserModUpdateListener.onPlayReady(handler.getPlayer()));
this.onGeyserInitialize();
+
+ var sourceConverter = CommandSourceConverter.layered(
+ CommandSourceStack.class,
+ id -> getServer().getPlayerList().getPlayer(id),
+ Player::createCommandSourceStack,
+ () -> getServer().createCommandSourceStack(), // NPE if method reference is used, since server is not available yet
+ ModCommandSource::new
+ );
+ CommandManager cloud = new FabricServerCommandManager<>(
+ ExecutionCoordinator.simpleCoordinator(),
+ sourceConverter
+ );
+ this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud, false)); // applying root permission would be a breaking change because we can't register permission defaults
}
@Override
public boolean isServer() {
return FabricLoader.getInstance().getEnvironmentType().equals(EnvType.SERVER);
}
-
- @Override
- public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
- return Permissions.check(source, permissionNode);
- }
-
- @Override
- public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
- return Permissions.check(source, permissionNode, permissionLevel);
- }
}
diff --git a/bootstrap/mod/fabric/src/main/resources/fabric.mod.json b/bootstrap/mod/fabric/src/main/resources/fabric.mod.json
index 93f48b73c..262f9833a 100644
--- a/bootstrap/mod/fabric/src/main/resources/fabric.mod.json
+++ b/bootstrap/mod/fabric/src/main/resources/fabric.mod.json
@@ -23,8 +23,8 @@
"geyser.mixins.json"
],
"depends": {
- "fabricloader": ">=0.15.10",
+ "fabricloader": ">=0.15.11",
"fabric": "*",
- "minecraft": ">=1.20.5"
+ "minecraft": ">=1.21"
}
}
diff --git a/bootstrap/mod/neoforge/build.gradle.kts b/bootstrap/mod/neoforge/build.gradle.kts
index f7204332b..a3b132494 100644
--- a/bootstrap/mod/neoforge/build.gradle.kts
+++ b/bootstrap/mod/neoforge/build.gradle.kts
@@ -1,16 +1,18 @@
plugins {
- application
+ id("geyser.modded-conventions")
+ id("geyser.modrinth-uploading-conventions")
}
-// This is provided by "org.cloudburstmc.math.mutable" too, so yeet.
-// NeoForge's class loader is *really* annoying.
-provided("org.cloudburstmc.math", "api")
-
architectury {
platformSetupLoomIde()
neoForge()
}
+// This is provided by "org.cloudburstmc.math.mutable" too, so yeet.
+// NeoForge's class loader is *really* annoying.
+provided("org.cloudburstmc.math", "api")
+provided("com.google.errorprone", "error_prone_annotations")
+
val includeTransitive: Configuration = configurations.getByName("includeTransitive")
dependencies {
@@ -34,10 +36,13 @@ dependencies {
// Include all transitive deps of core via JiJ
includeTransitive(projects.core)
+
+ modImplementation(libs.cloud.neoforge)
+ include(libs.cloud.neoforge)
}
-application {
- mainClass.set("org.geysermc.geyser.platform.forge.GeyserNeoForgeMain")
+tasks.withType {
+ manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.neoforge.GeyserNeoForgeMain"
}
tasks {
@@ -52,4 +57,5 @@ tasks {
modrinth {
loaders.add("neoforge")
-}
\ No newline at end of file
+ uploadFile.set(tasks.getByPath("remapModrinthJar"))
+}
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java
index 1655dea91..ad56eda39 100644
--- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java
@@ -27,6 +27,8 @@ package org.geysermc.geyser.platform.neoforge;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.world.entity.player.Player;
+import net.neoforged.bus.api.EventPriority;
+import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.loading.FMLLoader;
import net.neoforged.neoforge.common.NeoForge;
@@ -34,17 +36,24 @@ import net.neoforged.neoforge.event.GameShuttingDownEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import net.neoforged.neoforge.event.server.ServerStartedEvent;
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
-import org.checkerframework.checker.nullness.qual.NonNull;
+import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
+import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
+import org.geysermc.geyser.command.CommandSourceConverter;
+import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
+import org.geysermc.geyser.platform.mod.command.ModCommandSource;
+import org.incendo.cloud.CommandManager;
+import org.incendo.cloud.execution.ExecutionCoordinator;
+import org.incendo.cloud.neoforge.NeoForgeServerCommandManager;
+
+import java.util.Objects;
@Mod(ModConstants.MOD_ID)
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
@@ -55,9 +64,26 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
NeoForge.EVENT_BUS.addListener(this::onServerStopping);
NeoForge.EVENT_BUS.addListener(this::onPlayerJoin);
- NeoForge.EVENT_BUS.addListener(this.permissionHandler::onPermissionGather);
+
+ NeoForge.EVENT_BUS.addListener(EventPriority.HIGHEST, this::onPermissionGather);
this.onGeyserInitialize();
+
+ var sourceConverter = CommandSourceConverter.layered(
+ CommandSourceStack.class,
+ id -> getServer().getPlayerList().getPlayer(id),
+ Player::createCommandSourceStack,
+ () -> getServer().createCommandSourceStack(),
+ ModCommandSource::new
+ );
+ CommandManager cloud = new NeoForgeServerCommandManager<>(
+ ExecutionCoordinator.simpleCoordinator(),
+ sourceConverter
+ );
+ GeyserNeoForgeCommandRegistry registry = new GeyserNeoForgeCommandRegistry(getGeyser(), cloud);
+ this.setCommandRegistry(registry);
+ // An auxiliary listener for registering undefined permissions belonging to commands. See javadocs for more info.
+ NeoForge.EVENT_BUS.addListener(EventPriority.LOWEST, registry::onPermissionGatherForUndefined);
}
private void onServerStarted(ServerStartedEvent event) {
@@ -86,13 +112,17 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
return FMLLoader.getDist().isDedicatedServer();
}
- @Override
- public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
- return this.permissionHandler.hasPermission(source, permissionNode);
- }
+ private void onPermissionGather(PermissionGatherEvent.Nodes event) {
+ getGeyser().eventBus().fire(
+ (GeyserRegisterPermissionsEvent) (permission, defaultValue) -> {
+ Objects.requireNonNull(permission, "permission");
+ Objects.requireNonNull(defaultValue, "permission default for " + permission);
- @Override
- public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
- return this.permissionHandler.hasPermission(source, permissionNode, permissionLevel);
+ if (permission.isBlank()) {
+ return;
+ }
+ PermissionUtils.register(permission, defaultValue, event);
+ }
+ );
}
}
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeCommandRegistry.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeCommandRegistry.java
new file mode 100644
index 000000000..a8854d5d9
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeCommandRegistry.java
@@ -0,0 +1,101 @@
+/*
+ * 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.platform.neoforge;
+
+import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
+import org.geysermc.geyser.api.util.TriState;
+import org.geysermc.geyser.command.CommandRegistry;
+import org.geysermc.geyser.command.GeyserCommand;
+import org.geysermc.geyser.command.GeyserCommandSource;
+import org.incendo.cloud.CommandManager;
+import org.incendo.cloud.neoforge.PermissionNotRegisteredException;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class GeyserNeoForgeCommandRegistry extends CommandRegistry {
+
+ /**
+ * Permissions with an undefined permission default. Use Set to not register the same fallback more than once.
+ * NeoForge requires that all permissions are registered, and cloud-neoforge follows that.
+ * This is unlike most platforms, on which we wouldn't register a permission if no default was provided.
+ */
+ private final Set undefinedPermissions = new HashSet<>();
+
+ public GeyserNeoForgeCommandRegistry(GeyserImpl geyser, CommandManager cloud) {
+ super(geyser, cloud);
+ }
+
+ @Override
+ protected void register(GeyserCommand command, Map commands) {
+ super.register(command, commands);
+
+ // FIRST STAGE: Collect all permissions that may have undefined defaults.
+ if (!command.permission().isBlank() && command.permissionDefault() == null) {
+ // Permission requirement exists but no default value specified.
+ undefinedPermissions.add(command.permission());
+ }
+ }
+
+ @Override
+ protected void onRegisterPermissions(GeyserRegisterPermissionsEvent event) {
+ super.onRegisterPermissions(event);
+
+ // SECOND STAGE
+ // Now that we are aware of all commands, we can eliminate some incorrect assumptions.
+ // Example: two commands may have the same permission, but only of them defines a permission default.
+ undefinedPermissions.removeAll(permissionDefaults.keySet());
+ }
+
+ /**
+ * Registers permissions with possibly undefined defaults.
+ * Should be subscribed late to allow extensions and mods to register a desired permission default first.
+ */
+ void onPermissionGatherForUndefined(PermissionGatherEvent.Nodes event) {
+ // THIRD STAGE
+ for (String permission : undefinedPermissions) {
+ if (PermissionUtils.register(permission, TriState.NOT_SET, event)) {
+ // The permission was not already registered
+ geyser.getLogger().debug("Registered permission " + permission + " with fallback default value of NOT_SET");
+ }
+ }
+ }
+
+ @Override
+ public boolean hasPermission(GeyserCommandSource source, String permission) {
+ // NeoForgeServerCommandManager will throw this exception if the permission is not registered to the server.
+ // We can't realistically ensure that every permission is registered (calls by API users), so we catch this.
+ // This works for our calls, but not for cloud's internal usage. For that case, see above.
+ try {
+ return super.hasPermission(source, permission);
+ } catch (PermissionNotRegisteredException e) {
+ return false;
+ }
+ }
+}
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java
deleted file mode 100644
index 0a5f8f052..000000000
--- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (c) 2019-2023 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.neoforge;
-
-import net.minecraft.commands.CommandSourceStack;
-import net.minecraft.server.level.ServerPlayer;
-import net.minecraft.world.entity.player.Player;
-import net.neoforged.neoforge.server.permission.PermissionAPI;
-import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
-import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey;
-import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
-import net.neoforged.neoforge.server.permission.nodes.PermissionType;
-import net.neoforged.neoforge.server.permission.nodes.PermissionTypes;
-import org.checkerframework.checker.nullness.qual.NonNull;
-import org.geysermc.geyser.Constants;
-import org.geysermc.geyser.GeyserImpl;
-import org.geysermc.geyser.api.command.Command;
-import org.geysermc.geyser.command.GeyserCommandManager;
-
-import java.lang.reflect.Constructor;
-import java.util.HashMap;
-import java.util.Map;
-
-public class GeyserNeoForgePermissionHandler {
-
- private static final Constructor> PERMISSION_NODE_CONSTRUCTOR;
-
- static {
- try {
- @SuppressWarnings("rawtypes")
- Constructor constructor = PermissionNode.class.getDeclaredConstructor(
- String.class,
- PermissionType.class,
- PermissionNode.PermissionResolver.class,
- PermissionDynamicContextKey[].class
- );
- constructor.setAccessible(true);
- PERMISSION_NODE_CONSTRUCTOR = constructor;
- } catch (NoSuchMethodException e) {
- throw new RuntimeException("Unable to construct PermissionNode!", e);
- }
- }
-
- private final Map> permissionNodes = new HashMap<>();
-
- public void onPermissionGather(PermissionGatherEvent.Nodes event) {
- this.registerNode(Constants.UPDATE_PERMISSION, event);
-
- GeyserCommandManager commandManager = GeyserImpl.getInstance().commandManager();
- for (Map.Entry entry : commandManager.commands().entrySet()) {
- Command command = entry.getValue();
-
- // Don't register aliases
- if (!command.name().equals(entry.getKey())) {
- continue;
- }
-
- this.registerNode(command.permission(), event);
- }
-
- for (Map commands : commandManager.extensionCommands().values()) {
- for (Map.Entry entry : commands.entrySet()) {
- Command command = entry.getValue();
-
- // Don't register aliases
- if (!command.name().equals(entry.getKey())) {
- continue;
- }
-
- this.registerNode(command.permission(), event);
- }
- }
- }
-
- public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
- PermissionNode node = this.permissionNodes.get(permissionNode);
- if (node == null) {
- GeyserImpl.getInstance().getLogger().warning("Unable to find permission node " + permissionNode);
- return false;
- }
-
- return PermissionAPI.getPermission((ServerPlayer) source, node);
- }
-
- public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
- if (!source.isPlayer()) {
- return true;
- }
- assert source.getPlayer() != null;
- boolean permission = this.hasPermission(source.getPlayer(), permissionNode);
- if (!permission) {
- return source.getPlayer().hasPermissions(permissionLevel);
- }
-
- return true;
- }
-
- private void registerNode(String node, PermissionGatherEvent.Nodes event) {
- PermissionNode permissionNode = this.createNode(node);
-
- // NeoForge likes to crash if you try and register a duplicate node
- if (!event.getNodes().contains(permissionNode)) {
- event.addNodes(permissionNode);
- this.permissionNodes.put(node, permissionNode);
- }
- }
-
- @SuppressWarnings("unchecked")
- private PermissionNode createNode(String node) {
- // The typical constructors in PermissionNode require a
- // mod id, which means our permission nodes end up becoming
- // geyser_neoforge. instead of just . We work around
- // this by using reflection to access the constructor that
- // doesn't require a mod id or ResourceLocation.
- try {
- return (PermissionNode) PERMISSION_NODE_CONSTRUCTOR.newInstance(
- node,
- PermissionTypes.BOOLEAN,
- (PermissionNode.PermissionResolver) (player, playerUUID, context) -> false,
- new PermissionDynamicContextKey[0]
- );
- } catch (Exception e) {
- throw new RuntimeException("Unable to create permission node " + node, e);
- }
- }
-}
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.java
index 63abe4a4a..41562baf3 100644
--- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.java
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.java
@@ -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;
+ }
}
}
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/PermissionUtils.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/PermissionUtils.java
new file mode 100644
index 000000000..c57dc9a6c
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/PermissionUtils.java
@@ -0,0 +1,79 @@
+/*
+ * 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.neoforge;
+
+import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
+import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
+import net.neoforged.neoforge.server.permission.nodes.PermissionTypes;
+import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
+import org.geysermc.geyser.api.util.TriState;
+import org.geysermc.geyser.platform.neoforge.mixin.PermissionNodeMixin;
+
+/**
+ * Common logic for handling the more complicated way we have to register permission on NeoForge
+ */
+public class PermissionUtils {
+
+ private PermissionUtils() {
+ //no
+ }
+
+ /**
+ * Registers the given permission and its default value to the event. If the permission has the same name as one
+ * that has already been registered to the event, it will not be registered. In other words, it will not override.
+ *
+ * @param permission the permission to register
+ * @param permissionDefault the permission's default value. See {@link GeyserRegisterPermissionsEvent#register(String, TriState)} for TriState meanings.
+ * @param event the registration event
+ * @return true if the permission was registered
+ */
+ public static boolean register(String permission, TriState permissionDefault, PermissionGatherEvent.Nodes event) {
+ // NeoForge likes to crash if you try and register a duplicate node
+ if (event.getNodes().stream().noneMatch(n -> n.getNodeName().equals(permission))) {
+ PermissionNode node = createNode(permission, permissionDefault);
+ event.addNodes(node);
+ return true;
+ }
+ return false;
+ }
+
+ private static PermissionNode createNode(String node, TriState permissionDefault) {
+ return PermissionNodeMixin.geyser$construct(
+ node,
+ PermissionTypes.BOOLEAN,
+ (player, playerUUID, context) -> switch (permissionDefault) {
+ case TRUE -> true;
+ case FALSE -> false;
+ case NOT_SET -> {
+ if (player != null) {
+ yield player.createCommandSourceStack().hasPermission(player.server.getOperatorUserPermissionLevel());
+ }
+ yield false; // NeoForge javadocs say player is null in the case of an offline player.
+ }
+ }
+ );
+ }
+}
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/mixin/PermissionNodeMixin.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/mixin/PermissionNodeMixin.java
new file mode 100644
index 000000000..a43acd58a
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/mixin/PermissionNodeMixin.java
@@ -0,0 +1,48 @@
+/*
+ * 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.platform.neoforge.mixin;
+
+import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey;
+import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
+import net.neoforged.neoforge.server.permission.nodes.PermissionType;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Invoker;
+
+@Mixin(value = PermissionNode.class, remap = false) // this is API - do not remap
+public interface PermissionNodeMixin {
+
+ /**
+ * Invokes the matching private constructor in {@link PermissionNode}.
+ *
+ * The typical constructors in PermissionNode require a mod id, which means our permission nodes
+ * would end up becoming {@code geyser_neoforge.} instead of just {@code }.
+ */
+ @SuppressWarnings("rawtypes") // the varargs
+ @Invoker("")
+ static PermissionNode geyser$construct(String nodeName, PermissionType type, PermissionNode.PermissionResolver defaultResolver, PermissionDynamicContextKey... dynamics) {
+ throw new IllegalStateException();
+ }
+}
diff --git a/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml
index ff2823aa2..56b7d68e1 100644
--- a/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml
+++ b/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml
@@ -11,15 +11,17 @@ authors="GeyserMC"
description="${description}"
[[mixins]]
config = "geyser.mixins.json"
+[[mixins]]
+config = "geyser_neoforge.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"
\ No newline at end of file
diff --git a/bootstrap/mod/neoforge/src/main/resources/geyser_neoforge.mixins.json b/bootstrap/mod/neoforge/src/main/resources/geyser_neoforge.mixins.json
new file mode 100644
index 000000000..f1653051c
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/resources/geyser_neoforge.mixins.json
@@ -0,0 +1,12 @@
+{
+ "required": true,
+ "minVersion": "0.8",
+ "package": "org.geysermc.geyser.platform.neoforge.mixin",
+ "compatibilityLevel": "JAVA_17",
+ "mixins": [
+ "PermissionNodeMixin"
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java
index 96bbc9bb4..b1ba49942 100644
--- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java
@@ -25,31 +25,21 @@
package org.geysermc.geyser.platform.mod;
-import com.mojang.brigadier.arguments.StringArgumentType;
-import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
-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;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
-import org.geysermc.geyser.api.command.Command;
-import org.geysermc.geyser.api.extension.Extension;
-import org.geysermc.geyser.command.GeyserCommand;
-import org.geysermc.geyser.command.GeyserCommandManager;
+import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
-import org.geysermc.geyser.platform.mod.command.GeyserModCommandExecutor;
import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager;
import org.geysermc.geyser.text.GeyserLocale;
@@ -60,7 +50,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.SocketAddress;
import java.nio.file.Path;
-import java.util.Map;
import java.util.UUID;
@RequiredArgsConstructor
@@ -71,16 +60,18 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
private final GeyserModPlatform platform;
+ @Getter
private GeyserImpl geyser;
private Path dataFolder;
- @Setter
+ @Setter @Getter
private MinecraftServer server;
- private GeyserCommandManager geyserCommandManager;
+ @Setter
+ private CommandRegistry commandRegistry;
private GeyserModConfiguration geyserConfig;
private GeyserModInjector geyserInjector;
- private GeyserModLogger geyserLogger;
+ private final GeyserModLogger geyserLogger = new GeyserModLogger();
private IGeyserPingPassthrough geyserPingPassthrough;
private WorldManager geyserWorldManager;
@@ -92,16 +83,17 @@ 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, null);
-
- // Create command manager here, since the permission handler on neo needs it
- this.geyserCommandManager = new GeyserCommandManager(geyser);
- this.geyserCommandManager.init();
}
public void onGeyserEnable() {
+ // "Disabling" a mod isn't possible; so if we fail to initialize we need to manually stop here
+ if (geyser == null) {
+ return;
+ }
+
if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) {
return;
@@ -131,50 +123,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
if (isServer()) {
this.geyserInjector.initializeLocalChannel(this);
}
-
- // Start command building
- // Set just "geyser" as the help command
- GeyserModCommandExecutor helpExecutor = new GeyserModCommandExecutor(geyser,
- (GeyserCommand) geyser.commandManager().getCommands().get("help"));
- LiteralArgumentBuilder builder = Commands.literal("geyser").executes(helpExecutor);
-
- // Register all subcommands as valid
- for (Map.Entry command : geyser.commandManager().getCommands().entrySet()) {
- GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
- builder.then(Commands.literal(command.getKey())
- .executes(executor)
- // Could also test for Bedrock but depending on when this is called it may backfire
- .requires(executor::testPermission)
- // Allows parsing of arguments; e.g. for /geyser dump logs or the connectiontest command
- .then(Commands.argument("args", StringArgumentType.greedyString())
- .executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
- .requires(executor::testPermission)));
- }
- server.getCommands().getDispatcher().register(builder);
-
- // Register extension commands
- for (Map.Entry> extensionMapEntry : geyser.commandManager().extensionCommands().entrySet()) {
- Map extensionCommands = extensionMapEntry.getValue();
- if (extensionCommands.isEmpty()) {
- continue;
- }
-
- // Register help command for just "/"
- GeyserModCommandExecutor extensionHelpExecutor = new GeyserModCommandExecutor(geyser,
- (GeyserCommand) extensionCommands.get("help"));
- LiteralArgumentBuilder extCmdBuilder = Commands.literal(extensionMapEntry.getKey().description().id()).executes(extensionHelpExecutor);
-
- for (Map.Entry command : extensionCommands.entrySet()) {
- GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
- extCmdBuilder.then(Commands.literal(command.getKey())
- .executes(executor)
- .requires(executor::testPermission)
- .then(Commands.argument("args", StringArgumentType.greedyString())
- .executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
- .requires(executor::testPermission)));
- }
- server.getCommands().getDispatcher().register(extCmdBuilder);
- }
}
@Override
@@ -207,8 +155,8 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
}
@Override
- public GeyserCommandManager getGeyserCommandManager() {
- return geyserCommandManager;
+ public CommandRegistry getCommandRegistry() {
+ return commandRegistry;
}
@Override
@@ -236,6 +184,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
return this.server.getServerVersion();
}
+ @SuppressWarnings("ConstantConditions") // Certain IDEA installations think that ip cannot be null
@NonNull
@Override
public String getServerBindAddress() {
@@ -271,10 +220,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
return this.platform.resolveResource(resource);
}
- public abstract boolean hasPermission(@NonNull Player source, @NonNull String permissionNode);
-
- public abstract boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel);
-
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean loadConfig() {
try {
@@ -288,7 +233,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;
}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java
index 444b725e9..9260288d7 100644
--- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java
@@ -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);
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java
index 11ca0bc4f..6a724155f 100644
--- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java
@@ -25,17 +25,18 @@
package org.geysermc.geyser.platform.mod;
-import net.minecraft.commands.CommandSourceStack;
import net.minecraft.world.entity.player.Player;
-import org.geysermc.geyser.Constants;
-import org.geysermc.geyser.platform.mod.command.ModCommandSender;
+import org.geysermc.geyser.Permissions;
+import org.geysermc.geyser.platform.mod.command.ModCommandSource;
import org.geysermc.geyser.util.VersionCheckUtils;
public final class GeyserModUpdateListener {
public static void onPlayReady(Player player) {
- CommandSourceStack stack = player.createCommandSourceStack();
- if (GeyserModBootstrap.getInstance().hasPermission(stack, Constants.UPDATE_PERMISSION, 2)) {
- VersionCheckUtils.checkForGeyserUpdate(() -> new ModCommandSender(stack));
+ // Should be creating this in the supplier, but we need it for the permission check.
+ // Not a big deal currently because ModCommandSource doesn't load locale, so don't need to try to wait for it.
+ ModCommandSource source = new ModCommandSource(player.createCommandSourceStack());
+ if (source.hasPermission(Permissions.CHECK_UPDATE)) {
+ VersionCheckUtils.checkForGeyserUpdate(() -> source);
}
}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java
deleted file mode 100644
index 694dc732e..000000000
--- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (c) 2019-2022 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.mod.command;
-
-import com.mojang.brigadier.Command;
-import com.mojang.brigadier.context.CommandContext;
-import net.minecraft.commands.CommandSourceStack;
-import org.geysermc.geyser.GeyserImpl;
-import org.geysermc.geyser.command.GeyserCommand;
-import org.geysermc.geyser.command.GeyserCommandExecutor;
-import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
-import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.text.ChatColor;
-import org.geysermc.geyser.text.GeyserLocale;
-
-import java.util.Collections;
-
-public class GeyserModCommandExecutor extends GeyserCommandExecutor implements Command {
- private final GeyserCommand command;
-
- public GeyserModCommandExecutor(GeyserImpl geyser, GeyserCommand command) {
- super(geyser, Collections.singletonMap(command.name(), command));
- this.command = command;
- }
-
- public boolean testPermission(CommandSourceStack source) {
- return GeyserModBootstrap.getInstance().hasPermission(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0);
- }
-
- @Override
- public int run(CommandContext context) {
- return runWithArgs(context, "");
- }
-
- public int runWithArgs(CommandContext context, String args) {
- CommandSourceStack source = context.getSource();
- ModCommandSender sender = new ModCommandSender(source);
- GeyserSession session = getGeyserSession(sender);
- if (!testPermission(source)) {
- sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
- return 0;
- }
-
- if (command.isBedrockOnly() && session == null) {
- sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale()));
- return 0;
- }
-
- command.execute(session, sender, args.split(" "));
- return 0;
- }
-}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java
similarity index 77%
rename from bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java
rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java
index 5bebfae93..af1f368b3 100644
--- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java
@@ -31,19 +31,21 @@ import net.minecraft.core.RegistryAccess;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandSource;
-import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.text.ChatColor;
import java.util.Objects;
+import java.util.UUID;
-public class ModCommandSender implements GeyserCommandSource {
+public class ModCommandSource implements GeyserCommandSource {
private final CommandSourceStack source;
- public ModCommandSender(CommandSourceStack source) {
+ public ModCommandSource(CommandSourceStack source) {
this.source = source;
+ // todo find locale?
}
@Override
@@ -75,8 +77,24 @@ public class ModCommandSender implements GeyserCommandSource {
return !(source.getEntity() instanceof ServerPlayer);
}
+ @Override
+ public @Nullable UUID playerUuid() {
+ if (source.getEntity() instanceof ServerPlayer player) {
+ return player.getUUID();
+ }
+ return null;
+ }
+
@Override
public boolean hasPermission(String permission) {
- return GeyserModBootstrap.getInstance().hasPermission(source, permission, source.getServer().getOperatorUserPermissionLevel());
+ // Unlike other bootstraps; we delegate to cloud here too:
+ // On NeoForge; we'd have to keep track of all PermissionNodes - cloud already does that
+ // For Fabric, we won't need to include the Fabric Permissions API anymore - cloud already does that too :p
+ return GeyserImpl.getInstance().commandRegistry().hasPermission(this, permission);
+ }
+
+ @Override
+ public Object handle() {
+ return source;
}
}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/BlockPlaceMixin.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/BlockPlaceMixin.java
new file mode 100644
index 000000000..98620588e
--- /dev/null
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/BlockPlaceMixin.java
@@ -0,0 +1,79 @@
+/*
+ * 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.mod.mixin.server;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.BlockItem;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.context.BlockPlaceContext;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.SoundType;
+import net.minecraft.world.level.block.state.BlockState;
+import org.cloudburstmc.math.vector.Vector3f;
+import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
+import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.session.GeyserSession;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+@Mixin(BlockItem.class)
+public class BlockPlaceMixin {
+
+ @Inject(method = "place", locals = LocalCapture.CAPTURE_FAILSOFT, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;playSound(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/core/BlockPos;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FF)V"))
+ private void geyser$hijackPlaySound(BlockPlaceContext blockPlaceContext, CallbackInfoReturnable cir, BlockPlaceContext blockPlaceContext2, BlockState blockState, BlockPos blockPos, Level level, Player player, ItemStack itemStack, BlockState blockState2, SoundType soundType) {
+ if (player == null) {
+ return;
+ }
+
+ GeyserSession session = GeyserImpl.getInstance().connectionByUuid(player.getUUID());
+ if (session == null) {
+ return;
+ }
+
+ Vector3f position = Vector3f.from(
+ blockPos.getX(),
+ blockPos.getY(),
+ blockPos.getZ()
+ );
+
+ LevelSoundEventPacket placeBlockSoundPacket = new LevelSoundEventPacket();
+ placeBlockSoundPacket.setSound(SoundEvent.PLACE);
+ placeBlockSoundPacket.setPosition(position);
+ placeBlockSoundPacket.setBabySound(false);
+ placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(Block.BLOCK_STATE_REGISTRY.getId(blockState2)));
+ placeBlockSoundPacket.setIdentifier(":");
+ session.sendUpstreamPacket(placeBlockSoundPacket);
+ session.setLastBlockPlacePosition(null);
+ session.setLastBlockPlaced(null);
+ }
+}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/PistonBaseBlockMixin.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/PistonBaseBlockMixin.java
new file mode 100644
index 000000000..6ac51ba52
--- /dev/null
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/PistonBaseBlockMixin.java
@@ -0,0 +1,127 @@
+/*
+ * 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.mod.mixin.server;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.sugar.Share;
+import com.llamalad7.mixinextras.sugar.ref.LocalRef;
+import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.piston.PistonBaseBlock;
+import net.minecraft.world.level.block.state.BlockState;
+import org.cloudburstmc.math.vector.Vector3i;
+import org.geysermc.geyser.GeyserImpl;
+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 org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+@Mixin(PistonBaseBlock.class)
+public class PistonBaseBlockMixin {
+
+ @Shadow
+ @Final
+ private boolean isSticky;
+
+ @ModifyExpressionValue(method = "moveBlocks",
+ at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;")
+ )
+ private HashMap geyser$onMapCreate(HashMap original, @Share("pushBlocks") LocalRef