Merge remote-tracking branch 'refs/remotes/upstream/master' into build-number

# Conflicts:
#	core/build.gradle.kts
This commit is contained in:
onebeastchris 2024-06-21 01:08:18 +02:00
commit 40c67ca23c
710 changed files with 37123 additions and 24378 deletions

47
.github/workflows/build-remote.yml vendored Normal file
View file

@ -0,0 +1,47 @@
name: Build Remote
on:
workflow_call:
inputs:
repository:
required: true
description: 'The repo of the remote'
type: string
ref:
required: true
description: 'The ref of the remote'
type: string
permissions: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set Build Number
run: |
echo "BUILD_NUMBER=${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
- name: Setup Gradle
uses: GeyserMC/actions/setup-gradle-composite@master
with:
checkout_repository: ${{ inputs.repository }}
checkout_ref: ${{ inputs.ref }}
setup-java_java-version: 21
setup-gradle_cache-read-only: true
- name: Build Geyser
run: ./gradlew build
- name: Archive Artifacts
uses: GeyserMC/actions/upload-multi-artifact@master
if: success()
with:
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

View file

@ -3,9 +3,13 @@ name: Build
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches-ignore:
- 'gh-readonly-queue/**'
paths-ignore: paths-ignore:
- '.github/ISSUE_TEMPLATE/*.yml' - '.github/ISSUE_TEMPLATE/*.yml'
- '.github/actions/pullrequest.yml' - '.github/workflows/build-remote.yml'
- '.github/workflows/preview.yml'
- '.github/workflows/pull-request.yml'
- '.idea/copyright/*.xml' - '.idea/copyright/*.xml'
- '.gitignore' - '.gitignore'
- 'CONTRIBUTING.md' - 'CONTRIBUTING.md'
@ -18,112 +22,100 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository and submodules - name: Get Release Info
# See https://github.com/actions/checkout/commits id: release-info
uses: actions/checkout@72f2cec99f417b1a1c5e2e88945068983b7965f9 uses: GeyserMC/actions/previous-release@master
with: with:
submodules: recursive data: ${{ vars.RELEASEACTION_PREVRELEASE }}
- name: Validate Gradle Wrapper - name: Setup Gradle
# See https://github.com/gradle/wrapper-validation-action/commits uses: GeyserMC/actions/setup-gradle-composite@master
uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 with:
setup-java_java-version: 21
# See https://github.com/actions/setup-java/commits - name: Build Geyser
- uses: actions/setup-java@4075bfc1b51bf22876335ae1cd589602d60d8758 run: ./gradlew build
with: env:
java-version: 17 BUILD_NUMBER: ${{ steps.release-info.outputs.curentRelease }}
distribution: temurin
- name: Build - name: Archive Artifacts
# See https://github.com/gradle/gradle-build-action/commits uses: GeyserMC/actions/upload-multi-artifact@master
uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232
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@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success() if: success()
with: with:
name: Geyser Fabric artifacts: |
path: bootstrap/fabric/build/libs/Geyser-Fabric.jar bootstrap/mod/fabric/build/libs/Geyser-Fabric.jar
if-no-files-found: error bootstrap/mod/neoforge/build/libs/Geyser-NeoForge.jar
- name: Archive artifacts (Geyser Standalone) bootstrap/standalone/build/libs/Geyser-Standalone.jar
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 bootstrap/spigot/build/libs/Geyser-Spigot.jar
if: success() bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar
with: bootstrap/velocity/build/libs/Geyser-Velocity.jar
name: Geyser Standalone bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar
path: bootstrap/standalone/build/libs/Geyser-Standalone.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Spigot)
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
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@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
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@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success()
with:
name: Geyser Velocity
path: bootstrap/velocity/build/libs/Geyser-Velocity.jar
if-no-files-found: error
- name: Publish to Maven Repository - name: Publish to Maven Repository
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232 run: ./gradlew publish
env: env:
BUILD_NUMBER: ${{ steps.release-info.outputs.curentRelease }}
ORG_GRADLE_PROJECT_geysermcUsername: ${{ vars.DEPLOY_USER }} ORG_GRADLE_PROJECT_geysermcUsername: ${{ vars.DEPLOY_USER }}
ORG_GRADLE_PROJECT_geysermcPassword: ${{ secrets.DEPLOY_PASS }} ORG_GRADLE_PROJECT_geysermcPassword: ${{ secrets.DEPLOY_PASS }}
- 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() || failure()) && github.repository == 'GeyserMC/Geyser' }}
uses: GeyserMC/actions/release@master
id: metadata
with: with:
arguments: publish appID: ${{ secrets.RELEASE_APP_ID }}
appPrivateKey: ${{ secrets.RELEASE_APP_PK }}
files: |
bungeecord:bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar
fabric:bootstrap/mod/fabric/build/libs/Geyser-Fabric.jar
neoforge:bootstrap/mod/neoforge/build/libs/Geyser-NeoForge.jar
spigot:bootstrap/spigot/build/libs/Geyser-Spigot.jar
standalone:bootstrap/standalone/build/libs/Geyser-Standalone.jar
velocity:bootstrap/velocity/build/libs/Geyser-Velocity.jar
viaproxy:bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar
releaseEnabled: false
saveMetadata: true
releaseProject: 'geyser'
releaseVersion: ${{ steps.get-version.outputs.VERSION }}
- name: Publish to Downloads API - name: Publish to Downloads API
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
shell: bash uses: GeyserMC/actions/upload-release@master
env: with:
DOWNLOADS_USERNAME: ${{ vars.DOWNLOADS_USERNAME }} username: ${{ vars.DOWNLOADS_USERNAME }}
DOWNLOADS_PRIVATE_KEY: ${{ secrets.DOWNLOADS_PRIVATE_KEY }} privateKey: ${{ secrets.DOWNLOADS_PRIVATE_KEY }}
DOWNLOADS_SERVER_IP: ${{ secrets.DOWNLOADS_SERVER_IP }} host: ${{ secrets.DOWNLOADS_SERVER_IP }}
run: | files: |
# Save the private key to a file bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar
echo "$DOWNLOADS_PRIVATE_KEY" > id_ecdsa bootstrap/mod/fabric/build/libs/Geyser-Fabric.jar
chmod 600 id_ecdsa bootstrap/mod/neoforge/build/libs/Geyser-NeoForge.jar
# Set the project bootstrap/spigot/build/libs/Geyser-Spigot.jar
project=geyser bootstrap/standalone/build/libs/Geyser-Standalone.jar
# Get the version from gradle.properties bootstrap/velocity/build/libs/Geyser-Velocity.jar
version=$(cat gradle.properties | grep -o "version=[0-9\\.]*" | cut -d"=" -f2) bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar
# Create the build folder changelog: ${{ steps.metadata.outputs.body }}
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/
# Run the build script
# Push the metadata
echo "{\"project\": \"$project\", \"version\": \"$version\", \"id\": $GITHUB_RUN_NUMBER, \"commit\": \"$GITHUB_SHA\"}" > metadata.json
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" metadata.json $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$project/$GITHUB_RUN_NUMBER/
- name: Publish to Modrinth - name: Publish to Modrinth
uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
env: env:
CHANGELOG: ${{ steps.metadata.outputs.body }}
BUILD_NUMBER: ${{ steps.release-info.outputs.curentRelease }}
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
with: run: ./gradlew modrinth
arguments: fabric:modrinth
gradle-home-cache-cleanup: true
- name: Notify Discord - name: Notify Discord
if: ${{ (success() || failure()) && github.repository == 'GeyserMC/Geyser' }} if: ${{ (success() || failure()) && github.repository == 'GeyserMC/Geyser' }}
# See https://github.com/Tim203/actions-git-discord-webhook/commits uses: GeyserMC/actions/notify-discord@master
uses: Tim203/actions-git-discord-webhook@70f38ded3aca51635ec978ab4e1a58cd4cd0c2ff
with: with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK }} discordWebhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ job.status }} status: ${{ job.status }}
body: ${{ steps.metadata.outputs.body }}
includeDownloads: ${{ github.ref_name == 'master' }}

33
.github/workflows/dispatch-preview.yml vendored Normal file
View file

@ -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 }}

34
.github/workflows/pull-request.yml vendored Normal file
View file

@ -0,0 +1,34 @@
name: Process Pull Request
on:
pull_request_target:
jobs:
build:
# Forbid access to secrets nor GH Token perms while building the PR
permissions: {}
secrets: {}
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 }}
preview:
needs: [build]
if: >-
contains(github.event.pull_request.labels.*.name, 'PR: Needs Testing')
# Allow access to secrets if we are uploading a preview
secrets: inherit
uses: GeyserMC/actions/.github/workflows/upload-preview.yml@master
with:
build: ${{ github.run_number }}
version: pr.${{ github.event.pull_request.number }}
files: |
bungeecord:Geyser-BungeeCord.jar
fabric:Geyser-Fabric.jar
neoforge:Geyser-NeoForge.jar
spigot:Geyser-Spigot.jar
standalone:Geyser-Standalone.jar
velocity:Geyser-Velocity.jar
viaproxy:Geyser-ViaProxy.jar
project: geyserpreview
runId: ${{ github.run_id }}

View file

@ -1,89 +0,0 @@
name: Build Pull Request
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set up JDK 17
# See https://github.com/actions/setup-java/commits
uses: actions/setup-java@4075bfc1b51bf22876335ae1cd589602d60d8758
with:
java-version: 17
distribution: temurin
- name: Check if the author has forked the API repo
# See https://github.com/Kas-tle/find-forks-action/commits
uses: Kas-tle/find-forks-action@1b5447d1e3c7a8ed79583dd817cc5399686eed3a
id: find_forks
with:
owner: GeyserMC
repo: api
token: ${{ secrets.GITHUB_TOKEN }}
- name: Use author's API repo if it exists
if: ${{ steps.find_forks.outputs.target_branch_found == 'true' }}
env:
API_FORK_URL: ${{ steps.find_forks.outputs.user_fork_url }}
API_FORK_BRANCH: ${{ github.event.pull_request.head.ref }}
run: |
git clone "${API_FORK_URL}" --single-branch --branch "${API_FORK_BRANCH}" api
cd api
./gradlew publishToMavenLocal
- name: Checkout repository and submodules
# See https://github.com/actions/checkout/commits
uses: actions/checkout@72f2cec99f417b1a1c5e2e88945068983b7965f9
with:
submodules: recursive
path: geyser
- name: Validate Gradle Wrapper
# See https://github.com/gradle/wrapper-validation-action/commits
uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4
- name: Build Geyser
# See https://github.com/gradle/gradle-build-action/commits
uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232
with:
arguments: build
build-root-directory: geyser
- name: Archive artifacts (Geyser Fabric)
# See https://github.com/actions/upload-artifact/commits
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success()
with:
name: Geyser Fabric
path: geyser/bootstrap/fabric/build/libs/Geyser-Fabric.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Standalone)
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
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@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
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@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
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@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success()
with:
name: Geyser Velocity
path: geyser/bootstrap/velocity/build/libs/Geyser-Velocity.jar
if-no-files-found: error

View file

@ -1,6 +1,7 @@
<component name="CopyrightManager"> <component name="CopyrightManager">
<copyright> <copyright>
<option name="notice" value="Copyright (c) 2019-&amp;#36;today.year GeyserMC. http://geysermc.org&#10;&#10;Permission is hereby granted, free of charge, to any person obtaining a copy&#10;of this software and associated documentation files (the &quot;Software&quot;), to deal&#10;in the Software without restriction, including without limitation the rights&#10;to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&#10;copies of the Software, and to permit persons to whom the Software is&#10;furnished to do so, subject to the following conditions:&#10;&#10;The above copyright notice and this permission notice shall be included in&#10;all copies or substantial portions of the Software.&#10;&#10;THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&#10;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&#10;FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&#10;AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&#10;LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&#10;OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN&#10;THE SOFTWARE.&#10;&#10;@author GeyserMC&#10;@link https://github.com/GeyserMC/Geyser" /> <option name="allowReplaceRegexp" value="Copyright" />
<option name="notice" value="Copyright (c) &amp;#36;originalComment.match(&quot;Copyright \(c\) (\d+)&quot;, 1, &quot;-&quot;)&amp;#36;today.year GeyserMC. http://geysermc.org&#10;&#10;Permission is hereby granted, free of charge, to any person obtaining a copy&#10;of this software and associated documentation files (the &quot;Software&quot;), to deal&#10;in the Software without restriction, including without limitation the rights&#10;to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&#10;copies of the Software, and to permit persons to whom the Software is&#10;furnished to do so, subject to the following conditions:&#10;&#10;The above copyright notice and this permission notice shall be included in&#10;all copies or substantial portions of the Software.&#10;&#10;THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&#10;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&#10;FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&#10;AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&#10;LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&#10;OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN&#10;THE SOFTWARE.&#10;&#10;@author GeyserMC&#10;@link https://github.com/GeyserMC/Geyser" />
<option name="myName" value="Geyser" /> <option name="myName" value="Geyser" />
</copyright> </copyright>
</component> </component>

View file

@ -2,7 +2,7 @@
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](https://discord.gg/geysermc) [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](https://discord.gg/geysermc)
[![Crowdin](https://badges.crowdin.net/geyser/localized.svg)](https://translate.geysermc.org/) [![Crowdin](https://badges.crowdin.net/e/51361b7f8a01644a238d0fe8f3bddc62/localized.svg)](https://translate.geysermc.org/)
Geyser is a bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition, closing the gap from those wanting to play true cross-platform. Geyser is a bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition, closing the gap from those wanting to play true cross-platform.
@ -14,7 +14,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! 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.51 and Minecraft Java 1.20.4 ### Currently supporting Minecraft Bedrock 1.20.80 - 1.21.1 and Minecraft Java 1.21
## Setting Up ## Setting Up
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
@ -32,7 +32,6 @@ Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Ge
## What's Left to be Added/Fixed ## What's Left to be Added/Fixed
- Near-perfect movement (to the point where anticheat on large servers is unlikely to ban you) - Near-perfect movement (to the point where anticheat on large servers is unlikely to ban you)
- Some Entity Flags - Some Entity Flags
- Structure block UI
## What can't be fixed ## 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://wiki.geysermc.org/geyser/current-limitations/) page.

View file

@ -30,7 +30,7 @@ import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion; import javax.lang.model.SourceVersion;
@SupportedAnnotationTypes("*") @SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_16) @SupportedSourceVersion(SourceVersion.RELEASE_17)
public class BlockEntityProcessor extends ClassProcessor { public class BlockEntityProcessor extends ClassProcessor {
public BlockEntityProcessor() { public BlockEntityProcessor() {
super("org.geysermc.geyser.translator.level.block.entity.BlockEntity"); super("org.geysermc.geyser.translator.level.block.entity.BlockEntity");

View file

@ -30,7 +30,7 @@ import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion; import javax.lang.model.SourceVersion;
@SupportedAnnotationTypes("*") @SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_16) @SupportedSourceVersion(SourceVersion.RELEASE_17)
public class CollisionRemapperProcessor extends ClassProcessor { public class CollisionRemapperProcessor extends ClassProcessor {
public CollisionRemapperProcessor() { public CollisionRemapperProcessor() {
super("org.geysermc.geyser.translator.collision.CollisionRemapper"); super("org.geysermc.geyser.translator.collision.CollisionRemapper");

View file

@ -30,7 +30,7 @@ import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion; import javax.lang.model.SourceVersion;
@SupportedAnnotationTypes("*") @SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_16) @SupportedSourceVersion(SourceVersion.RELEASE_17)
public class PacketTranslatorProcessor extends ClassProcessor { public class PacketTranslatorProcessor extends ClassProcessor {
public PacketTranslatorProcessor() { public PacketTranslatorProcessor() {
super("org.geysermc.geyser.translator.protocol.Translator"); super("org.geysermc.geyser.translator.protocol.Translator");

View file

@ -30,7 +30,7 @@ import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion; import javax.lang.model.SourceVersion;
@SupportedAnnotationTypes("*") @SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_16) @SupportedSourceVersion(SourceVersion.RELEASE_17)
public class SoundHandlerProcessor extends ClassProcessor { public class SoundHandlerProcessor extends ClassProcessor {
public SoundHandlerProcessor() { public SoundHandlerProcessor() {
super("org.geysermc.geyser.translator.sound.SoundTranslator"); super("org.geysermc.geyser.translator.sound.SoundTranslator");

View file

@ -4,4 +4,5 @@ plugins {
dependencies { dependencies {
api(libs.base.api) api(libs.base.api)
api(libs.math)
} }

View file

@ -29,12 +29,14 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.Geyser; import org.geysermc.api.Geyser;
import org.geysermc.api.GeyserApiBase; import org.geysermc.api.GeyserApiBase;
import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.event.EventBus;
import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.event.EventRegistrar;
import org.geysermc.geyser.api.extension.ExtensionManager; import org.geysermc.geyser.api.extension.ExtensionManager;
import org.geysermc.geyser.api.network.BedrockListener; import org.geysermc.geyser.api.network.BedrockListener;
import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.api.network.RemoteServer;
import org.geysermc.geyser.api.util.MinecraftVersion;
import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.api.util.PlatformType;
import java.nio.file.Path; import java.nio.file.Path;
@ -134,6 +136,30 @@ public interface GeyserApi extends GeyserApiBase {
@NonNull @NonNull
PlatformType platformType(); PlatformType platformType();
/**
* Gets the version of Java Minecraft that is supported.
*
* @return the supported version of Java Minecraft
*/
@NonNull
MinecraftVersion supportedJavaVersion();
/**
* Gets a list of Bedrock Minecraft versions that are supported.
*
* @return the list of supported Bedrock Minecraft versions
*/
@NonNull
List<MinecraftVersion> supportedBedrockVersions();
/**
* Gets the {@link CommandSource} for the console.
*
* @return the console command source
*/
@NonNull
CommandSource consoleCommandSource();
/** /**
* Gets the current {@link GeyserApiBase} instance. * Gets the current {@link GeyserApiBase} instance.
* *

View file

@ -0,0 +1,180 @@
/*
* 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.bedrock.camera;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.connection.GeyserConnection;
import java.util.Set;
import java.util.UUID;
/**
* This interface holds all the methods that relate to a client's camera.
* Can be accessed through {@link GeyserConnection#camera()}.
*/
public interface CameraData {
/**
* Sends a camera fade instruction to the client.
* If an existing camera fade is already in progress, the current fade will be prolonged.
* Can be built using {@link CameraFade.Builder}.
* To stop a fade early, use {@link #clearCameraInstructions()}.
*
* @param fade the camera fade instruction to send
*/
void sendCameraFade(@NonNull CameraFade fade);
/**
* Sends a camera position instruction to the client.
* If an existing camera movement is already in progress,
* the final camera position will be the one of the latest instruction, and
* the (optional) camera fade will be added on top of the existing fade.
* Can be built using {@link CameraPosition.Builder}.
* To stop reset the camera position/stop ongoing instructions, use {@link #clearCameraInstructions()}.
*
* @param position the camera position instruction to send
*/
void sendCameraPosition(@NonNull CameraPosition position);
/**
* Stops all sent camera instructions (fades, movements, and perspective locks).
* This will not stop any camera shakes/input locks/fog effects, use the respective methods for those.
*/
void clearCameraInstructions();
/**
* Forces a {@link CameraPerspective} on the client. This will prevent the client
* from changing their camera perspective until it is unlocked via {@link #clearCameraInstructions()}.
* <p>
* Note: You cannot force a client into a free camera perspective with this method.
* To do that, send a {@link CameraPosition} via {@link #sendCameraPosition(CameraPosition)} - it requires a set position
* instead of being relative to the player.
*
* @param perspective the {@link CameraPerspective} to force
*/
void forceCameraPerspective(@NonNull CameraPerspective perspective);
/**
* Gets the client's current {@link CameraPerspective}, if one is currently forced.
* This will return {@code null} if the client is not currently forced into a perspective.
* If a perspective is forced, the client will not be able to change their camera perspective until it is unlocked.
*
* @return the forced perspective, or {@code null} if none is forced
*/
@Nullable CameraPerspective forcedCameraPerspective();
/**
* Shakes the client's camera.
* <p>
* If the camera is already shaking with the same {@link CameraShake} type, then the additional intensity
* will be layered on top of the existing intensity, with their own distinct durations.<br>
* If the existing shake type is different and the new intensity/duration are not positive, the existing shake only
* switches to the new type. Otherwise, the existing shake is completely overridden.
*
* @param intensity the intensity of the shake. The client has a maximum total intensity of 4.
* @param duration the time in seconds that the shake will occur for
* @param type the type of shake
*/
void shakeCamera(float intensity, float duration, @NonNull CameraShake type);
/**
* Stops all camera shakes of any type.
*/
void stopCameraShake();
/**
* Adds the given fog IDs to the fog cache, then sends all fog IDs in the cache to the client.
* <p>
* Fog IDs can be found <a href="https://wiki.bedrock.dev/documentation/fog-ids.html">here</a>
*
* @param fogNameSpaces the fog IDs to add. If empty, the existing cached IDs will still be sent.
*/
void sendFog(String... fogNameSpaces);
/**
* Removes the given fog IDs from the fog cache, then sends all fog IDs in the cache to the client.
*
* @param fogNameSpaces the fog IDs to remove. If empty, all fog IDs will be removed.
*/
void removeFog(String... fogNameSpaces);
/**
* Returns an immutable copy of all fog affects currently applied to this client.
*/
@NonNull
Set<String> fogEffects();
/**
* (Un)locks the client's camera, so that they cannot look around.
* To ensure the camera is only unlocked when all locks are released, you must supply
* a UUID when using method, and use the same UUID to unlock the camera.
*
* @param lock whether to lock the camera
* @param owner the owner of the lock, represented with a UUID
* @return if the camera is locked after this method call
*/
boolean lockCamera(boolean lock, @NonNull UUID owner);
/**
* Returns whether the client's camera is locked.
*
* @return whether the camera is currently locked
*/
boolean isCameraLocked();
/**
* Hides a {@link GuiElement} on the client's side.
*
* @param element the {@link GuiElement} to hide
*/
void hideElement(@NonNull GuiElement... element);
/**
* Resets a {@link GuiElement} on the client's side.
* This makes the client decide on its own - e.g. based on client settings -
* whether to show or hide the gui element.
* <p>
* If no elements are specified, this will reset all currently hidden elements
*
* @param element the {@link GuiElement} to reset
*/
void resetElement(@NonNull GuiElement @Nullable... element);
/**
* Determines whether a {@link GuiElement} is currently hidden.
*
* @param element the {@link GuiElement} to check
*/
boolean isHudElementHidden(@NonNull GuiElement element);
/**
* Returns the currently hidden {@link GuiElement}s.
*
* @return an unmodifiable view of all currently hidden {@link GuiElement}s
*/
@NonNull Set<GuiElement> hiddenElements();
}

View file

@ -0,0 +1,77 @@
/*
* 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.bedrock.camera;
/**
* These are all the easing types that can be used when sending a {@link CameraPosition} instruction.
* When using these, the client won't teleport to the new camera position, but instead transition to it.
* <p>
* See <a href="https://easings.net/">https://easings.net/</a> for more information.
*/
public enum CameraEaseType {
LINEAR("linear"),
SPRING("spring"),
EASE_IN_SINE("in_sine"),
EASE_OUT_SINE("out_sine"),
EASE_IN_OUT_SINE("in_out_sine"),
EASE_IN_QUAD("in_quad"),
EASE_OUT_QUAD("out_quad"),
EASE_IN_OUT_QUAD("in_out_quad"),
EASE_IN_CUBIC("in_cubic"),
EASE_OUT_CUBIC("out_cubic"),
EASE_IN_OUT_CUBIC("in_out_cubic"),
EASE_IN_QUART("in_quart"),
EASE_OUT_QUART("out_quart"),
EASE_IN_OUT_QUART("in_out_quart"),
EASE_IN_QUINT("in_quint"),
EASE_OUT_QUINT("out_quint"),
EASE_IN_OUT_QUINT("in_out_quint"),
EASE_IN_EXPO("in_expo"),
EASE_OUT_EXPO("out_expo"),
EASE_IN_OUT_EXPO("in_out_expo"),
EASE_IN_CIRC("in_circ"),
EASE_OUT_CIRC("out_circ"),
EASE_IN_OUT_CIRC("in_out_circ"),
EASE_IN_BACK("in_back"),
EASE_OUT_BACK("out_back"),
EASE_IN_OUT_BACK("in_out_back"),
EASE_IN_ELASTIC("in_elastic"),
EASE_OUT_ELASTIC("out_elastic"),
EASE_IN_OUT_ELASTIC("in_out_elastic"),
EASE_IN_BOUNCE("in_bounce"),
EASE_OUT_BOUNCE("out_bounce"),
EASE_IN_OUT_BOUNCE("in_out_bounce");
private final String id;
CameraEaseType(String id) {
this.id = id;
}
public String id() {
return this.id;
}
}

View file

@ -0,0 +1,94 @@
/*
* 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.bedrock.camera;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.common.value.qual.IntRange;
import org.geysermc.geyser.api.GeyserApi;
import java.awt.Color;
/**
* Represents a coloured fade overlay on the camera.
* <p>
* Can be sent with {@link CameraData#sendCameraFade(CameraFade)}, or with a {@link CameraPosition} instruction.
*/
public interface CameraFade {
/**
* Gets the color overlay of the camera.
* Bedrock uses an RGB color system.
*
* @return the color of the fade
*/
@NonNull Color color();
/**
* Gets the seconds it takes to fade in.
* All fade times combined must take at least 0.5 seconds, and at most 30 seconds.
*
* @return the seconds it takes to fade in
*/
float fadeInSeconds();
/**
* Gets the seconds the overlay is held.
* All fade times combined must take at least 0.5 seconds, and at most 30 seconds.
*
* @return the seconds the overlay is held
*/
float fadeHoldSeconds();
/**
* Gets the seconds it takes to fade out.
* All fade times combined must take at least 0.5 seconds, and at most 30 seconds.
*
* @return the seconds it takes to fade out
*/
float fadeOutSeconds();
/**
* Creates a Builder for CameraFade
*
* @return a CameraFade Builder
*/
static CameraFade.Builder builder() {
return GeyserApi.api().provider(CameraFade.Builder.class);
}
interface Builder {
Builder color(@NonNull Color color);
Builder fadeInSeconds(@IntRange(from = 0, to = 10) float fadeInSeconds);
Builder fadeHoldSeconds(@IntRange(from = 0, to = 10) float fadeHoldSeconds);
Builder fadeOutSeconds(@IntRange(from = 0, to = 10) float fadeOutSeconds);
CameraFade build();
}
}

View file

@ -0,0 +1,48 @@
/*
* 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.bedrock.camera;
/**
* Represents a camera perspective that a player's camera can take.
* All perspectives except for {@link #FREE} are locked to the player's head,
* and are therefore relative to the player's position and rotation.
*/
public enum CameraPerspective {
FIRST_PERSON("minecraft:first_person"),
FREE("minecraft:free"),
THIRD_PERSON("minecraft:third_person"),
THIRD_PERSON_FRONT("minecraft:third_person_front");
private final String id;
CameraPerspective(String id) {
this.id = id;
}
public String id() {
return this.id;
}
}

View file

@ -0,0 +1,150 @@
/*
* 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.bedrock.camera;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.value.qual.IntRange;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.api.GeyserApi;
/**
* This interface represents a camera position instruction. Can be built with the {@link #builder()}.
* <p>
* Any camera position instruction pins the client camera to a specific position and rotation.
* You can set {@link CameraEaseType} to ensure a smooth transition that will last {@link #easeSeconds()} seconds.
* A {@link CameraFade} can also be sent, which will transition the player to a coloured transition during the transition.
* <p>
* Use {@link CameraData#sendCameraPosition(CameraPosition)} to send such an instruction to any connection.
*/
public interface CameraPosition {
/**
* Gets the camera's position.
*
* @return camera position vector
*/
@NonNull Vector3f position();
/**
* Gets the {@link CameraEaseType} of the camera.
* If not set, there is no easing.
*
* @return camera ease type
*/
@Nullable CameraEaseType easeType();
/**
* Gets the {@link CameraFade} to be sent along the camera position instruction.
* If set, they will run at once.
*
* @return camera fade, or null if not present
*/
@Nullable CameraFade cameraFade();
/**
* Gets the easing duration of the camera, in seconds.
* Is only used if a {@link CameraEaseType} is set.
*
* @return camera easing duration in seconds
*/
float easeSeconds();
/**
* Gets the x-axis rotation of the camera.
* To prevent the camera from being upside down, Bedrock limits the range to -90 to 90.
* Will be overridden if {@link #facingPosition()} is set.
*
* @return camera x-axis rotation
*/
@IntRange(from = -90, to = 90) int rotationX();
/**
* Gets the y-axis rotation of the camera.
* Will be overridden if {@link #facingPosition()} is set.
*
* @return camera y-axis rotation
*/
int rotationY();
/**
* Gets the position that the camera is facing.
* Can be used instead of manually setting rotation values.
* <p>
* If set, the rotation values set via {@link #rotationX()} and {@link #rotationY()} will be ignored.
*
* @return Camera's facing position
*/
@Nullable Vector3f facingPosition();
/**
* Controls whether player effects, such as night vision or blindness, should be rendered on the camera.
* Defaults to false.
*
* @return whether player effects should be rendered
*/
boolean renderPlayerEffects();
/**
* Controls whether the player position should be used for directional audio.
* If false, the camera position will be used instead.
*
* @return whether the players position should be used for directional audio
*/
boolean playerPositionForAudio();
/**
* Creates a Builder for CameraPosition
*
* @return a CameraPosition Builder
*/
static CameraPosition.Builder builder() {
return GeyserApi.api().provider(CameraPosition.Builder.class);
}
interface Builder {
Builder cameraFade(@Nullable CameraFade cameraFade);
Builder renderPlayerEffects(boolean renderPlayerEffects);
Builder playerPositionForAudio(boolean playerPositionForAudio);
Builder easeType(@Nullable CameraEaseType easeType);
Builder easeSeconds(float easeSeconds);
Builder position(@NonNull Vector3f position);
Builder rotationX(@IntRange(from = -90, to = 90) int rotationX);
Builder rotationY(int rotationY);
Builder facingPosition(@Nullable Vector3f facingPosition);
CameraPosition build();
}
}

View file

@ -25,6 +25,9 @@
package org.geysermc.geyser.api.bedrock.camera; package org.geysermc.geyser.api.bedrock.camera;
/**
* Represents a camera shake instruction. Can be sent in {@link CameraData#shakeCamera(float, float, CameraShake)}
*/
public enum CameraShake { public enum CameraShake {
POSITIONAL, POSITIONAL,
ROTATIONAL ROTATIONAL

View file

@ -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;
}
}

View file

@ -61,16 +61,16 @@ public interface CustomBlockData {
boolean includedInCreativeInventory(); boolean includedInCreativeInventory();
/** /**
* Gets the item's creative category, or tab id. * Gets the block's creative category, or tab id.
* *
* @return the item's creative category * @return the block's creative category
*/ */
@Nullable CreativeCategory creativeCategory(); @Nullable CreativeCategory creativeCategory();
/** /**
* Gets the item's creative group. * Gets the block's creative group.
* *
* @return the item's creative group * @return the block's creative group
*/ */
@Nullable String creativeGroup(); @Nullable String creativeGroup();

View file

@ -129,8 +129,11 @@ public interface CustomBlockComponents {
* Gets the unit cube component * Gets the unit cube component
* Equivalent to "minecraft:unit_cube" * Equivalent to "minecraft:unit_cube"
* *
* @deprecated Use {@link #geometry()} and compare with `minecraft:geometry.full_block` instead.
*
* @return The rotation. * @return The rotation.
*/ */
@Deprecated
boolean unitCube(); boolean unitCube();
/** /**
@ -181,6 +184,10 @@ public interface CustomBlockComponents {
Builder transformation(TransformationComponent transformation); Builder transformation(TransformationComponent transformation);
/**
* @deprecated Use {@link #geometry(GeometryComponent)} with `minecraft:geometry.full_block` instead.
*/
@Deprecated
Builder unitCube(boolean unitCube); Builder unitCube(boolean unitCube);
Builder placeAir(boolean placeAir); Builder placeAir(boolean placeAir);

View file

@ -73,7 +73,10 @@ public interface JavaBlockState {
* Gets whether the block state has block entity * Gets whether the block state has block entity
* *
* @return 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(); boolean hasBlockEntity();
/** /**
@ -104,6 +107,11 @@ public interface JavaBlockState {
Builder pistonBehavior(@Nullable String pistonBehavior); 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); Builder hasBlockEntity(boolean hasBlockEntity);
JavaBlockState build(); JavaBlockState build();

View file

@ -29,8 +29,10 @@ import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.connection.Connection; import org.geysermc.api.connection.Connection;
import org.geysermc.geyser.api.bedrock.camera.CameraData;
import org.geysermc.geyser.api.bedrock.camera.CameraShake; import org.geysermc.geyser.api.bedrock.camera.CameraShake;
import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.entity.EntityData;
import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.api.entity.type.GeyserEntity;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
@ -41,10 +43,29 @@ import java.util.concurrent.CompletableFuture;
* Represents a player connection used in Geyser. * Represents a player connection used in Geyser.
*/ */
public interface GeyserConnection extends Connection, CommandSource { public interface GeyserConnection extends Connection, CommandSource {
/**
* Exposes the {@link CameraData} for this connection.
* It allows you to send fogs, camera shakes, force camera perspectives, and more.
*
* @return the CameraData for this connection.
*/
@NonNull CameraData camera();
/**
* Exposes the {@link EntityData} for this connection.
* It allows you to get entities by their Java entity ID, show emotes, and get the player entity.
*
* @return the EntityData for this connection.
*/
@NonNull EntityData entities();
/** /**
* @param javaId the Java entity ID to look up. * @param javaId the Java entity ID to look up.
* @return a {@link GeyserEntity} if present in this connection's entity tracker. * @return a {@link GeyserEntity} if present in this connection's entity tracker.
* @deprecated Use {@link EntityData#entityByJavaId(int)} instead
*/ */
@Deprecated
@NonNull @NonNull
CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId); CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId);
@ -53,11 +74,14 @@ public interface GeyserConnection extends Connection, CommandSource {
* *
* @param emoter the player entity emoting. * @param emoter the player entity emoting.
* @param emoteId the emote ID to send to this client. * @param emoteId the emote ID to send to this client.
* @deprecated use {@link EntityData#showEmote(GeyserPlayerEntity, String)} instead
*/ */
@Deprecated
void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId); void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId);
/** /**
* Shakes the client's camera.<br><br> * Shakes the client's camera.
* <p>
* If the camera is already shaking with the same {@link CameraShake} type, then the additional intensity * If the camera is already shaking with the same {@link CameraShake} type, then the additional intensity
* will be layered on top of the existing intensity, with their own distinct durations.<br> * will be layered on top of the existing intensity, with their own distinct durations.<br>
* If the existing shake type is different and the new intensity/duration are not positive, the existing shake only * If the existing shake type is different and the new intensity/duration are not positive, the existing shake only
@ -66,12 +90,18 @@ public interface GeyserConnection extends Connection, CommandSource {
* @param intensity the intensity of the shake. The client has a maximum total intensity of 4. * @param intensity the intensity of the shake. The client has a maximum total intensity of 4.
* @param duration the time in seconds that the shake will occur for * @param duration the time in seconds that the shake will occur for
* @param type the type of shake * @param type the type of shake
*
* @deprecated Use {@link CameraData#shakeCamera(float, float, CameraShake)} instead.
*/ */
@Deprecated
void shakeCamera(float intensity, float duration, @NonNull CameraShake type); void shakeCamera(float intensity, float duration, @NonNull CameraShake type);
/** /**
* Stops all camera shake of any type. * Stops all camera shake of any type.
*
* @deprecated Use {@link CameraData#stopCameraShake()} instead.
*/ */
@Deprecated
void stopCameraShake(); void stopCameraShake();
/** /**
@ -80,19 +110,26 @@ public interface GeyserConnection extends Connection, CommandSource {
* Fog IDs can be found <a href="https://wiki.bedrock.dev/documentation/fog-ids.html">here</a> * Fog IDs can be found <a href="https://wiki.bedrock.dev/documentation/fog-ids.html">here</a>
* *
* @param fogNameSpaces the fog IDs to add. If empty, the existing cached IDs will still be sent. * @param fogNameSpaces the fog IDs to add. If empty, the existing cached IDs will still be sent.
* @deprecated Use {@link CameraData#sendFog(String...)} instead.
*/ */
@Deprecated
void sendFog(String... fogNameSpaces); void sendFog(String... fogNameSpaces);
/** /**
* Removes the given fog IDs from the fog cache, then sends all fog IDs in the cache to the client. * Removes the given fog IDs from the fog cache, then sends all fog IDs in the cache to the client.
* *
* @param fogNameSpaces the fog IDs to remove. If empty, all fog IDs will be removed. * @param fogNameSpaces the fog IDs to remove. If empty, all fog IDs will be removed.
* @deprecated Use {@link CameraData#removeFog(String...)} instead.
*/ */
@Deprecated
void removeFog(String... fogNameSpaces); void removeFog(String... fogNameSpaces);
/** /**
* Returns an immutable copy of all fog affects currently applied to this client. * Returns an immutable copy of all fog affects currently applied to this client.
*
* @deprecated Use {@link CameraData#fogEffects()} instead.
*/ */
@Deprecated
@NonNull @NonNull
Set<String> fogEffects(); Set<String> fogEffects();
} }

View file

@ -0,0 +1,84 @@
/*
* 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.entity;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.entity.type.GeyserEntity;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
* This class holds all the methods that relate to entities.
* Can be accessed through {@link GeyserConnection#entities()}.
*/
public interface EntityData {
/**
* Returns a {@link GeyserEntity} to e.g. make them play an emote.
*
* @param javaId the Java entity ID to look up
* @return a {@link GeyserEntity} if present in this connection's entity tracker
*/
@NonNull CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId);
/**
* Displays a player entity as emoting to this client.
*
* @param emoter the player entity emoting
* @param emoteId the emote ID to send to this client
*/
void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId);
/**
* Gets the {@link GeyserPlayerEntity} of this connection.
*
* @return the {@link GeyserPlayerEntity} of this connection
*/
@NonNull GeyserPlayerEntity playerEntity();
/**
* (Un)locks the client's movement inputs, so that they cannot move.
* To ensure that movement is only unlocked when all locks are released, you must supply
* a UUID with this method, and use the same UUID to unlock the camera.
*
* @param lock whether to lock the movement
* @param owner the owner of the lock
* @return if the movement is locked after this method call
*/
boolean lockMovement(boolean lock, @NonNull UUID owner);
/**
* Returns whether the client's movement is currently locked.
*
* @return whether the movement is locked
*/
boolean isMovementLocked();
}

View file

@ -25,7 +25,15 @@
package org.geysermc.geyser.api.entity.type.player; package org.geysermc.geyser.api.entity.type.player;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.api.entity.type.GeyserEntity;
public interface GeyserPlayerEntity extends GeyserEntity { public interface GeyserPlayerEntity extends GeyserEntity {
/**
* Gets the position of the player.
*
* @return the position of the player.
*/
Vector3f position();
} }

View file

@ -32,18 +32,27 @@ import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.connection.ConnectionEvent; import org.geysermc.geyser.api.event.connection.ConnectionEvent;
import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.api.network.RemoteServer;
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. * 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 { public final class SessionLoginEvent extends ConnectionEvent implements Cancellable {
private RemoteServer remoteServer; private RemoteServer remoteServer;
private boolean cancelled; private boolean cancelled;
private String disconnectReason; private String disconnectReason;
private Map<String, byte[]> cookies;
private boolean transferring;
public SessionLoginEvent(@NonNull GeyserConnection connection, @NonNull RemoteServer remoteServer) { public SessionLoginEvent(@NonNull GeyserConnection connection,
@NonNull RemoteServer remoteServer,
@NonNull Map<String, byte[]> cookies) {
super(connection); super(connection);
this.remoteServer = remoteServer; this.remoteServer = remoteServer;
this.cookies = cookies;
this.transferring = false;
} }
/** /**
@ -90,9 +99,9 @@ public final class SessionLoginEvent extends ConnectionEvent implements Cancella
} }
/** /**
* Gets the {@link RemoteServer} the section will attempt to connect to. * Gets the {@link RemoteServer} the session will attempt to connect to.
* *
* @return the {@link RemoteServer} the section will attempt to connect to. * @return the {@link RemoteServer} the session will attempt to connect to.
*/ */
public @NonNull RemoteServer remoteServer() { public @NonNull RemoteServer remoteServer() {
return this.remoteServer; return this.remoteServer;
@ -106,4 +115,36 @@ public final class SessionLoginEvent extends ConnectionEvent implements Cancella
public void remoteServer(@NonNull RemoteServer remoteServer) { public void remoteServer(@NonNull RemoteServer remoteServer) {
this.remoteServer = remoteServer; this.remoteServer = remoteServer;
} }
/**
* Sets a map of cookies from a possible previous session. The Java server can send and request these
* to store information on the client across server transfers.
*/
public void cookies(@NonNull Map<String, byte[]> cookies) {
Objects.requireNonNull(cookies);
this.cookies = cookies;
}
/**
* Gets a map of the sessions cookies, if set.
* @return the connections cookies
*/
public @NonNull Map<String, byte[]> cookies() {
return cookies;
}
/**
* Determines the connection intent of the connection
*/
public void transferring(boolean transferring) {
this.transferring = transferring;
}
/**
* Gets whether this login attempt to the Java server
* has the transfer intent
*/
public boolean transferring() {
return this.transferring;
}
} }

View file

@ -0,0 +1,144 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.event.bedrock;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
import org.geysermc.geyser.api.skin.Cape;
import org.geysermc.geyser.api.skin.Skin;
import org.geysermc.geyser.api.skin.SkinData;
import org.geysermc.geyser.api.skin.SkinGeometry;
import java.util.UUID;
/**
* Called when a skin is applied to a player.
* <p>
* Won't be called when a fake player is spawned for a player skull.
*/
public abstract class SessionSkinApplyEvent extends ConnectionEvent {
private final String username;
private final UUID uuid;
private final boolean slim;
private final boolean bedrock;
private final SkinData originalSkinData;
public SessionSkinApplyEvent(@NonNull GeyserConnection connection, String username, UUID uuid, boolean slim, boolean bedrock, SkinData skinData) {
super(connection);
this.username = username;
this.uuid = uuid;
this.slim = slim;
this.bedrock = bedrock;
this.originalSkinData = skinData;
}
/**
* The username of the player.
*
* @return the username of the player
*/
public @NonNull String username() {
return username;
}
/**
* The UUID of the player.
*
* @return the UUID of the player
*/
public @NonNull UUID uuid() {
return uuid;
}
/**
* If the player is using a slim model.
*
* @return if the player is using a slim model
*/
public boolean slim() {
return slim;
}
/**
* If the player is a Bedrock player.
*
* @return if the player is a Bedrock player
*/
public boolean bedrock() {
return bedrock;
}
/**
* The original skin data of the player.
*
* @return the original skin data of the player
*/
public @NonNull SkinData originalSkin() {
return originalSkinData;
}
/**
* The skin data of the player.
*
* @return the current skin data of the player
*/
public abstract @NonNull SkinData skinData();
/**
* Change the skin of the player.
*
* @param newSkin the new skin
*/
public abstract void skin(@NonNull Skin newSkin);
/**
* Change the cape of the player.
*
* @param newCape the new cape
*/
public abstract void cape(@NonNull Cape newCape);
/**
* Change the geometry of the player.
*
* @param newGeometry the new geometry
*/
public abstract void geometry(@NonNull SkinGeometry newGeometry);
/**
* Change the geometry of the player.
* <p>
* Constructs a generic {@link SkinGeometry} object with the given data.
*
* @param geometryName the name of the geometry
* @param geometryData the data of the geometry
*/
public void geometry(@NonNull String geometryName, @NonNull String geometryData) {
geometry(new SkinGeometry("{\"geometry\" :{\"default\" :\"" + geometryName + "\"}}", geometryData));
}
}

View file

@ -0,0 +1,110 @@
/*
* 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.connection;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.event.Cancellable;
import org.geysermc.event.Event;
import java.net.InetSocketAddress;
/**
* Called whenever a client attempts to connect to the server, before the connection is accepted.
*/
public final class ConnectionRequestEvent implements Event, Cancellable {
private boolean cancelled;
private final InetSocketAddress ip;
private final InetSocketAddress proxyIp;
public ConnectionRequestEvent(@NonNull InetSocketAddress ip, @Nullable InetSocketAddress proxyIp) {
this.ip = ip;
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 inetSocketAddress() {
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
*/
@Nullable
public InetSocketAddress proxyIp() {
return proxyIp;
}
/**
* The cancel status of this event. If this event is cancelled, the connection will be rejected.
*
* @return the cancel status of this event
*/
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* Sets the cancel status of this event. If this event is canceled, the connection will be rejected.
*
* @param cancelled the cancel status of this event.
*/
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}

View file

@ -0,0 +1,125 @@
/*
* 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.java;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.value.qual.IntRange;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
import java.util.Map;
/**
* Fired when the Java server sends a transfer request to a different Java server.
* Geyser Extensions can listen to this event and set a target server ip/port for Bedrock players to be transferred to.
*/
public class ServerTransferEvent extends ConnectionEvent {
private final String host;
private final int port;
private String bedrockHost;
private int bedrockPort;
private final Map<String, byte[]> cookies;
public ServerTransferEvent(@NonNull GeyserConnection connection,
@NonNull String host, int port, @NonNull Map<String, byte[]> cookies) {
super(connection);
this.host = host;
this.port = port;
this.cookies = cookies;
this.bedrockHost = null;
this.bedrockPort = -1;
}
/**
* The host that the Java server requests a transfer to.
*
* @return the host
*/
public @NonNull String host() {
return this.host;
}
/**
* The port that the Java server requests a transfer to.
*
* @return the port
*/
public int port() {
return this.port;
}
/**
* The host that the Bedrock player should try and connect to.
* If this is not set, the Bedrock player will just be disconnected.
*
* @return the host where the Bedrock client will be transferred to, or null if not set.
*/
public @Nullable String bedrockHost() {
return this.bedrockHost;
}
/**
* The port that the Bedrock player should try and connect to.
* If this is not set, the Bedrock player will just be disconnected.
*
* @return the port where the Bedrock client will be transferred to, or -1 if not set.
*/
public int bedrockPort() {
return this.bedrockPort;
}
/**
* Sets the host for the Bedrock player to be transferred to
*/
public void bedrockHost(@NonNull String host) {
if (host == null || host.isBlank()) {
throw new IllegalArgumentException("Server address cannot be null or blank");
}
this.bedrockHost = host;
}
/**
* Sets the port for the Bedrock player to be transferred to
*/
public void bedrockPort(@IntRange(from = 0, to = 65535) int port) {
if (port < 0 || port > 65535) {
throw new IllegalArgumentException("Server port must be between 0 and 65535, was " + port);
}
this.bedrockPort = port;
}
/**
* Gets a map of the sessions current cookies.
*
* @return the connections cookies
*/
public @NonNull Map<String, byte[]> cookies() {
return cookies;
}
}

View file

@ -0,0 +1,42 @@
/*
* 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.lifecycle;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.Event;
import org.geysermc.geyser.api.event.EventBus;
import org.geysermc.geyser.api.event.EventRegistrar;
import org.geysermc.geyser.api.extension.ExtensionManager;
/**
* Called when Geyser finished reloading and is accepting Bedrock connections again.
* Equivalent to the {@link GeyserPostInitializeEvent}
*
* @param extensionManager the extension manager
* @param eventBus the event bus
*/
public record GeyserPostReloadEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus<EventRegistrar> eventBus) implements Event {
}

View file

@ -0,0 +1,42 @@
/*
* 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.lifecycle;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.Event;
import org.geysermc.geyser.api.event.EventBus;
import org.geysermc.geyser.api.event.EventRegistrar;
import org.geysermc.geyser.api.extension.ExtensionManager;
/**
* Called when Geyser is about to reload. Primarily aimed at extensions, so they can decide on their own what to reload.
* After this event is fired, some lifecycle events can be fired again - such as the {@link GeyserLoadResourcePacksEvent}.
*
* @param extensionManager the extension manager
* @param eventBus the event bus
*/
public record GeyserPreReloadEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus<EventRegistrar> eventBus) implements Event {
}

View file

@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.GeyserApi;
import java.util.OptionalInt;
import java.util.Set; import java.util.Set;
/** /**
@ -77,6 +78,20 @@ public interface CustomItemData {
*/ */
boolean displayHandheld(); boolean displayHandheld();
/**
* Gets the item's creative category, or tab id.
*
* @return the item's creative category
*/
@NonNull OptionalInt creativeCategory();
/**
* Gets the item's creative group.
*
* @return the item's creative group
*/
@Nullable String creativeGroup();
/** /**
* Gets the item's texture size. This is to resize the item if the texture is not 16x16. * Gets the item's texture size. This is to resize the item if the texture is not 16x16.
* *
@ -119,6 +134,10 @@ public interface CustomItemData {
Builder displayHandheld(boolean displayHandheld); Builder displayHandheld(boolean displayHandheld);
Builder creativeCategory(int creativeCategory);
Builder creativeGroup(@Nullable String creativeGroup);
Builder textureSize(int textureSize); Builder textureSize(int textureSize);
Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets); Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -30,7 +30,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.GeyserApi;
import java.util.OptionalInt;
import java.util.Set; import java.util.Set;
/** /**
@ -65,6 +64,14 @@ public interface NonVanillaCustomItemData extends CustomItemData {
*/ */
int maxDamage(); int maxDamage();
/**
* Gets the attack damage of the item.
* This is purely visual, and only applied to tools
*
* @return the attack damage of the item
*/
int attackDamage();
/** /**
* Gets the tool type of the item. * Gets the tool type of the item.
* *
@ -107,20 +114,6 @@ public interface NonVanillaCustomItemData extends CustomItemData {
*/ */
@Nullable Set<String> repairMaterials(); @Nullable Set<String> repairMaterials();
/**
* Gets the item's creative category, or tab id.
*
* @return the item's creative category
*/
@NonNull OptionalInt creativeCategory();
/**
* Gets the item's creative group.
*
* @return the item's creative group
*/
@Nullable String creativeGroup();
/** /**
* Gets if the item is a hat. This is used to determine if the item should be rendered on the player's head, and * Gets if the item is a hat. This is used to determine if the item should be rendered on the player's head, and
* normally allow the player to equip it. This is not meant for armor. * normally allow the player to equip it. This is not meant for armor.
@ -168,6 +161,13 @@ public interface NonVanillaCustomItemData extends CustomItemData {
return displayHandheld(); return displayHandheld();
} }
/**
* Gets the block the item places.
*
* @return the block the item places
*/
String block();
static NonVanillaCustomItemData.Builder builder() { static NonVanillaCustomItemData.Builder builder() {
return GeyserApi.api().provider(NonVanillaCustomItemData.Builder.class); return GeyserApi.api().provider(NonVanillaCustomItemData.Builder.class);
} }
@ -184,6 +184,8 @@ public interface NonVanillaCustomItemData extends CustomItemData {
Builder maxDamage(int maxDamage); Builder maxDamage(int maxDamage);
Builder attackDamage(int attackDamage);
Builder toolType(@Nullable String toolType); Builder toolType(@Nullable String toolType);
Builder toolTier(@Nullable String toolTier); Builder toolTier(@Nullable String toolTier);
@ -196,10 +198,6 @@ public interface NonVanillaCustomItemData extends CustomItemData {
Builder repairMaterials(@Nullable Set<String> repairMaterials); Builder repairMaterials(@Nullable Set<String> repairMaterials);
Builder creativeCategory(int creativeCategory);
Builder creativeGroup(@Nullable String creativeGroup);
Builder hat(boolean isHat); Builder hat(boolean isHat);
Builder foil(boolean isFoil); Builder foil(boolean isFoil);
@ -210,6 +208,8 @@ public interface NonVanillaCustomItemData extends CustomItemData {
Builder chargeable(boolean isChargeable); Builder chargeable(boolean isChargeable);
Builder block(String block);
/** /**
* @deprecated Use {@link #displayHandheld(boolean)} instead. * @deprecated Use {@link #displayHandheld(boolean)} instead.
*/ */
@ -218,6 +218,12 @@ public interface NonVanillaCustomItemData extends CustomItemData {
return displayHandheld(isTool); return displayHandheld(isTool);
} }
@Override
Builder creativeCategory(int creativeCategory);
@Override
Builder creativeGroup(@Nullable String creativeGroup);
@Override @Override
Builder customItemOptions(@NonNull CustomItemOptions customItemOptions); Builder customItemOptions(@NonNull CustomItemOptions customItemOptions);

View file

@ -50,6 +50,14 @@ public interface BedrockListener {
*/ */
int port(); int port();
/**
* Gets the broadcast port that's sent to Bedrock clients with the motd.
* This is the port that Bedrock clients will connect with. It usually does not differ from the listening port.
*
* @return the broadcast port
*/
int broadcastPort();
/** /**
* Gets the primary MOTD shown to Bedrock players if a ping passthrough setting is not enabled. * Gets the primary MOTD shown to Bedrock players if a ping passthrough setting is not enabled.
* <p> * <p>

View file

@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -23,18 +23,18 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.level.block; package org.geysermc.geyser.api.skin;
/** /**
* This stores all values of double chests that are part of the Java block state. * Represents a cape.
* *
* @param isFacingEast If true, then chest is facing east/west; if false, south/north * @param textureUrl The URL of the cape texture
* @param isDirectionPositive If true, direction is positive (east/south); if false, direction is negative (west/north) * @param capeId The ID of the cape
* @param isLeft If true, chest is the left of a pair; if false, chest is the right of a pair. * @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
*/ */
public record DoubleChestValue( public record Cape(String textureUrl, String capeId, byte[] capeData, boolean failed) {
boolean isFacingEast, public Cape(String textureUrl, String capeId, byte[] capeData) {
boolean isDirectionPositive, this(textureUrl, capeId, capeData, false);
boolean isLeft) { }
} }

View file

@ -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);
}
}

View file

@ -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) {
}

View file

@ -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" : "") + "\"}}", "");
}
}

View file

@ -1,5 +1,5 @@
/* /*
* 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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -31,11 +31,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
* Represents the creative menu categories or tabs. * Represents the creative menu categories or tabs.
*/ */
public enum CreativeCategory { public enum CreativeCategory {
COMMANDS("commands", 1), CONSTRUCTION("construction", 1),
CONSTRUCTION("construction", 2), NATURE("nature", 2),
EQUIPMENT("equipment", 3), EQUIPMENT("equipment", 3),
ITEMS("items", 4), ITEMS("items", 4),
NATURE("nature", 5),
NONE("none", 6); NONE("none", 6);
private final String internalName; private final String internalName;

View file

@ -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.util;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Represents a Minecraft version.
*/
public interface MinecraftVersion {
/**
* Gets the Minecraft version as a String.
* Example: "1.20.2", or "1.20.40/1.20.41"
*
* @return the version string
*/
@NonNull String versionString();
/**
* Gets the protocol version of this Minecraft version.
*
* @return the protocol version
*/
int protocolVersion();
}

View file

@ -34,10 +34,12 @@ public record PlatformType(String platformName) {
public static final PlatformType ANDROID = new PlatformType("Android"); public static final PlatformType ANDROID = new PlatformType("Android");
public static final PlatformType BUNGEECORD = new PlatformType("BungeeCord"); public static final PlatformType BUNGEECORD = new PlatformType("BungeeCord");
public static final PlatformType FABRIC = new PlatformType("Fabric"); public static final PlatformType FABRIC = new PlatformType("Fabric");
public static final PlatformType NEOFORGE = new PlatformType("NeoForge");
public static final PlatformType SPIGOT = new PlatformType("Spigot"); public static final PlatformType SPIGOT = new PlatformType("Spigot");
@Deprecated @Deprecated
public static final PlatformType SPONGE = new PlatformType("Sponge"); public static final PlatformType SPONGE = new PlatformType("Sponge");
public static final PlatformType STANDALONE = new PlatformType("Standalone"); public static final PlatformType STANDALONE = new PlatformType("Standalone");
public static final PlatformType VELOCITY = new PlatformType("Velocity"); public static final PlatformType VELOCITY = new PlatformType("Velocity");
public static final PlatformType VIAPROXY = new PlatformType("ViaProxy");
} }

View file

@ -1,7 +1,7 @@
dependencies { dependencies {
api(projects.core) api(projects.core)
implementation(libs.adventure.text.serializer.bungeecord) implementation(libs.adventure.text.serializer.bungeecord)
compileOnlyApi(libs.bungeecord.proxy)
} }
platformRelocate("net.md_5.bungee.jni") platformRelocate("net.md_5.bungee.jni")
@ -22,6 +22,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
dependencies { dependencies {
exclude(dependency("com.google.*:.*")) exclude(dependency("com.google.*:.*"))
exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("io.netty:netty-transport-native-epoll:.*")) exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-unix-common:.*")) exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
exclude(dependency("io.netty:netty-handler:.*")) exclude(dependency("io.netty:netty-handler:.*"))
@ -33,3 +34,8 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
exclude(dependency("io.netty:netty-resolver-dns:.*")) exclude(dependency("io.netty:netty-resolver-dns:.*"))
} }
} }
modrinth {
uploadFile.set(tasks.getByPath("shadowJar"))
loaders.add("bungeecord")
}

View file

@ -26,22 +26,19 @@
package org.geysermc.geyser.platform.bungeecord; package org.geysermc.geyser.platform.bungeecord;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.GeyserLogger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@RequiredArgsConstructor
public class GeyserBungeeLogger implements GeyserLogger { public class GeyserBungeeLogger implements GeyserLogger {
private final Logger logger; private final Logger logger;
@Getter @Setter @Getter @Setter
private boolean debug; private boolean debug;
public GeyserBungeeLogger(Logger logger, boolean debug) {
this.logger = logger;
this.debug = debug;
}
@Override @Override
public void severe(String message) { public void severe(String message) {
logger.severe(message); logger.severe(message);

View file

@ -32,11 +32,11 @@ import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.protocol.ProtocolConstants; import net.md_5.bungee.protocol.ProtocolConstants;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.dump.BootstrapDumpInfo;
@ -58,62 +58,96 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
private GeyserCommandManager geyserCommandManager; private GeyserCommandManager geyserCommandManager;
private GeyserBungeeConfiguration geyserConfig; private GeyserBungeeConfiguration geyserConfig;
private GeyserBungeeInjector geyserInjector; private GeyserBungeeInjector geyserInjector;
private GeyserBungeeLogger geyserLogger; private final GeyserBungeeLogger geyserLogger = new GeyserBungeeLogger(getLogger());
private IGeyserPingPassthrough geyserBungeePingPassthrough; private IGeyserPingPassthrough geyserBungeePingPassthrough;
private GeyserImpl geyser; private GeyserImpl geyser;
private static boolean INITIALIZED = false;
@SuppressWarnings({"JavaReflectionMemberAccess", "ResultOfMethodCallIgnored"})
@Override @Override
public void onLoad() { public void onLoad() {
onGeyserInitialize();
}
@Override
public void onGeyserInitialize() {
GeyserLocale.init(this); GeyserLocale.init(this);
// Copied from ViaVersion. // Copied from ViaVersion.
// https://github.com/ViaVersion/ViaVersion/blob/b8072aad86695cc8ec6f5e4103e43baf3abf6cc5/bungee/src/main/java/us/myles/ViaVersion/BungeePlugin.java#L43 // https://github.com/ViaVersion/ViaVersion/blob/b8072aad86695cc8ec6f5e4103e43baf3abf6cc5/bungee/src/main/java/us/myles/ViaVersion/BungeePlugin.java#L43
try { try {
ProtocolConstants.class.getField("MINECRAFT_1_20_3"); ProtocolConstants.class.getField("MINECRAFT_1_21");
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException e) {
getLogger().warning(" / \\"); geyserLogger.error(" / \\");
getLogger().warning(" / \\"); geyserLogger.error(" / \\");
getLogger().warning(" / | \\"); geyserLogger.error(" / | \\");
getLogger().warning(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName())); geyserLogger.error(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName()));
getLogger().warning(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps")); geyserLogger.error(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps"));
getLogger().warning(" / o \\"); geyserLogger.error(" / o \\");
getLogger().warning("/_____________\\"); geyserLogger.error("/_____________\\");
} }
if (!getDataFolder().exists()) if (!this.loadConfig()) {
getDataFolder().mkdir();
try {
if (!getDataFolder().exists())
getDataFolder().mkdir();
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"),
"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);
ex.printStackTrace();
return; return;
} }
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this); this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this);
this.geyserInjector = new GeyserBungeeInjector(this);
} }
@Override @Override
public void onEnable() { public void onEnable() {
// 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
this.awaitStartupCompletion(0);
}
@SuppressWarnings("unchecked")
private void awaitStartupCompletion(int tries) {
// After 20 tries give up waiting. This will happen just after 3 minutes approximately
if (tries >= 20) {
this.geyserLogger.warning("BungeeCord plugin startup is taking abnormally long, so Geyser is starting now. " +
"If all your plugins are loaded properly, this is a bug! " +
"If not, consider cutting down the amount of plugins on your proxy as it is causing abnormally slow starting times.");
this.onGeyserEnable();
return;
}
try {
Field listenersField = BungeeCord.getInstance().getClass().getDeclaredField("listeners");
listenersField.setAccessible(true);
Collection<Channel> listeners = (Collection<Channel>) listenersField.get(BungeeCord.getInstance());
if (listeners.isEmpty()) {
this.getProxy().getScheduler().schedule(this, this::onGeyserEnable, tries, TimeUnit.SECONDS);
} else {
this.awaitStartupCompletion(++tries);
}
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
}
}
public void onGeyserEnable() {
if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) {
return;
}
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 // Force-disable query if enabled, or else Geyser won't enable
for (ListenerInfo info : getProxy().getConfig().getListeners()) { for (ListenerInfo info : getProxy().getConfig().getListeners()) {
@ -133,54 +167,20 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
} }
} }
// Big hack - Bungee does not provide us an event to listen to, so schedule a repeating GeyserImpl.start();
// task that waits for a field to be filled which is set after the plugin enable
// process is complete
if (!INITIALIZED) {
this.awaitStartupCompletion(0);
} else {
// No need to "wait" for startup completion, just start Geyser - we're reloading.
this.postStartup();
}
}
@SuppressWarnings("unchecked") if (geyserConfig.isLegacyPingPassthrough()) {
private void awaitStartupCompletion(int tries) { this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
// After 20 tries give up waiting. This will happen } else {
// just after 3 minutes approximately this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy());
if (tries >= 20) { }
this.geyserLogger.warning("BungeeCord plugin startup is taking abnormally long, so Geyser is starting now. " +
"If all your plugins are loaded properly, this is a bug! " + // No need to re-register commands or re-init injector when reloading
"If not, consider cutting down the amount of plugins on your proxy as it is causing abnormally slow starting times."); if (GeyserImpl.getInstance().isReloading()) {
this.postStartup();
return; return;
} }
try { this.geyserInjector.initializeLocalChannel(this);
Field listenersField = BungeeCord.getInstance().getClass().getDeclaredField("listeners");
listenersField.setAccessible(true);
Collection<Channel> listeners = (Collection<Channel>) listenersField.get(BungeeCord.getInstance());
if (listeners.isEmpty()) {
this.getProxy().getScheduler().schedule(this, this::postStartup, tries, TimeUnit.SECONDS);
} else {
this.awaitStartupCompletion(++tries);
}
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
}
}
private void postStartup() {
GeyserImpl.start();
if (!INITIALIZED) {
this.geyserInjector = new GeyserBungeeInjector(this);
this.geyserInjector.initializeLocalChannel(this);
}
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands())); this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands()));
for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) { for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
@ -191,18 +191,17 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(entry.getKey().description().id(), this.geyser, commands)); this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(entry.getKey().description().id(), this.geyser, commands));
} }
if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else {
this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy());
}
INITIALIZED = true;
} }
@Override @Override
public void onDisable() { public void onGeyserDisable() {
if (geyser != null) {
geyser.disable();
}
}
@Override
public void onGeyserShutdown() {
if (geyser != null) { if (geyser != null) {
geyser.shutdown(); geyser.shutdown();
} }
@ -211,6 +210,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
} }
} }
@Override
public void onDisable() {
this.onGeyserShutdown();
}
@Override @Override
public GeyserBungeeConfiguration getGeyserConfig() { public GeyserBungeeConfiguration getGeyserConfig() {
return geyserConfig; return geyserConfig;
@ -278,4 +282,20 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
.map(info -> (InetSocketAddress) info.getSocketAddress()) .map(info -> (InetSocketAddress) info.getSocketAddress())
.findFirst(); .findFirst();
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean loadConfig() {
try {
if (!getDataFolder().exists()) //noinspection ResultOfMethodCallIgnored
getDataFolder().mkdir();
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"),
"config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class);
} catch (IOException ex) {
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
return false;
}
return true;
}
} }

View file

@ -1,124 +0,0 @@
import net.fabricmc.loom.task.RemapJarTask
plugins {
id("fabric-loom") version "1.0-SNAPSHOT"
id("com.modrinth.minotaur") version "2.+"
}
dependencies {
//to change the versions see the gradle.properties file
minecraft(libs.fabric.minecraft)
mappings(loom.officialMojangMappings())
modImplementation(libs.fabric.loader)
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation(libs.fabric.api)
// This should be in the libs TOML, but something about modImplementation AND include just doesn't work
include(modImplementation("me.lucko", "fabric-permissions-api", "0.2-SNAPSHOT"))
// PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
// You may need to force-disable transitiveness on them.
api(projects.core)
shadow(projects.core) {
exclude(group = "com.google.guava", module = "guava")
exclude(group = "com.google.code.gson", module = "gson")
exclude(group = "org.slf4j")
exclude(group = "com.nukkitx.fastutil")
exclude(group = "io.netty.incubator")
}
}
loom {
mixin.defaultRefmapName.set("geyser-fabric-refmap.json")
}
repositories {
mavenLocal()
maven("https://repo.opencollab.dev/maven-releases/")
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://jitpack.io")
maven("https://oss.sonatype.org/content/repositories/snapshots/")
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
}
application {
mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain")
}
tasks {
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this task, sources will not be generated.
sourcesJar {
archiveClassifier.set("sources")
from(sourceSets.main.get().allSource)
}
shadowJar {
// Mirrors the example fabric project, otherwise tons of dependencies are shaded that shouldn't be
configurations = listOf(project.configurations.shadow.get())
// The remapped shadowJar is the final desired Geyser-Fabric.jar
archiveVersion.set(project.version.toString())
archiveClassifier.set("shaded")
relocate("org.objectweb.asm", "org.geysermc.relocate.asm")
relocate("org.yaml", "org.geysermc.relocate.yaml") // https://github.com/CardboardPowered/cardboard/issues/139
relocate("com.fasterxml.jackson", "org.geysermc.relocate.jackson")
relocate("net.kyori", "org.geysermc.relocate.kyori")
dependencies {
// Exclude everything EXCEPT some DNS stuff required for HAProxy
exclude(dependency("io.netty:netty-transport-classes-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
exclude(dependency("io.netty:netty-transport-classes-kqueue:.*"))
exclude(dependency("io.netty:netty-transport-native-kqueue:.*"))
exclude(dependency("io.netty:netty-handler:.*"))
exclude(dependency("io.netty:netty-common:.*"))
exclude(dependency("io.netty:netty-buffer:.*"))
exclude(dependency("io.netty:netty-resolver:.*"))
exclude(dependency("io.netty:netty-transport:.*"))
exclude(dependency("io.netty:netty-codec:.*"))
exclude(dependency("io.netty:netty-resolver-dns:.*"))
exclude(dependency("io.netty:netty-resolver-dns-native-macos:.*"))
}
}
remapJar {
dependsOn(shadowJar)
inputFile.set(shadowJar.get().archiveFile)
archiveBaseName.set("Geyser-Fabric")
archiveVersion.set("")
archiveClassifier.set("")
}
register("remapModrinthJar", RemapJarTask::class) {
dependsOn(shadowJar)
inputFile.set(shadowJar.get().archiveFile)
archiveBaseName.set("geyser-fabric")
archiveVersion.set(project.version.toString() + "+build." + System.getenv("GITHUB_RUN_NUMBER"))
archiveClassifier.set("")
}
}
modrinth {
token.set(System.getenv("MODRINTH_TOKEN")) // Even though this is the default value, apparently this prevents GitHub Actions caching the token?
projectId.set("wKkoqHrH")
versionNumber.set(project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER"))
versionType.set("beta")
changelog.set("A changelog can be found at https://github.com/GeyserMC/Geyser/commits")
syncBodyFrom.set(rootProject.file("README.md").readText())
uploadFile.set(tasks.getByPath("remapModrinthJar"))
gameVersions.addAll("1.20.4")
loaders.add("fabric")
failSilently.set(true)
dependencies {
required.project("fabric-api")
}
}

View file

@ -1,289 +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.fabric.world;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.*;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.WritableBookItem;
import net.minecraft.world.item.WrittenBookItem;
import net.minecraft.world.level.block.entity.BannerBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.LecternBlockEntity;
import net.minecraft.world.level.chunk.LevelChunk;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType;
import org.geysermc.erosion.util.LecternUtils;
import org.geysermc.geyser.level.GeyserWorldManager;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.BlockEntityUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
public class GeyserFabricWorldManager extends GeyserWorldManager {
private final MinecraftServer server;
public GeyserFabricWorldManager(MinecraftServer server) {
this.server = server;
}
@Override
public boolean shouldExpectLecternHandled(GeyserSession session) {
return true;
}
@Override
public void sendLecternData(GeyserSession session, int x, int z, List<BlockEntityInfo> blockEntityInfos) {
server.execute(() -> {
ServerPlayer player = getPlayer(session);
if (player == null) {
return;
}
//noinspection resource - level() is just a getter
LevelChunk chunk = player.level().getChunk(x, z);
final int chunkBlockX = x << 4;
final int chunkBlockZ = z << 4;
//noinspection ForLoopReplaceableByForEach - avoid constructing iterator
for (int i = 0; i < blockEntityInfos.size(); i++) {
BlockEntityInfo blockEntityInfo = blockEntityInfos.get(i);
BlockEntity blockEntity = chunk.getBlockEntity(new BlockPos(chunkBlockX + blockEntityInfo.getX(),
blockEntityInfo.getY(), chunkBlockZ + blockEntityInfo.getZ()));
sendLecternData(session, blockEntity, true);
}
});
}
@Override
public void sendLecternData(GeyserSession session, int x, int y, int z) {
server.execute(() -> {
ServerPlayer player = getPlayer(session);
if (player == null) {
return;
}
//noinspection resource - level() is just a getter
BlockEntity blockEntity = player.level().getBlockEntity(new BlockPos(x, y, z));
sendLecternData(session, blockEntity, false);
});
}
private void sendLecternData(GeyserSession session, BlockEntity blockEntity, boolean isChunkLoad) {
if (!(blockEntity instanceof LecternBlockEntity lectern)) {
return;
}
int x = blockEntity.getBlockPos().getX();
int y = blockEntity.getBlockPos().getY();
int z = blockEntity.getBlockPos().getZ();
if (!lectern.hasBook()) {
if (!isChunkLoad) {
BlockEntityUtils.updateBlockEntity(session, LecternUtils.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z));
}
return;
}
ItemStack book = lectern.getBook();
int pageCount = WrittenBookItem.getPageCount(book);
boolean hasBookPages = pageCount > 0;
NbtMapBuilder lecternTag = LecternUtils.getBaseLecternTag(x, y, z, hasBookPages ? pageCount : 1);
lecternTag.putInt("page", lectern.getPage() / 2);
NbtMapBuilder bookTag = NbtMap.builder()
.putByte("Count", (byte) book.getCount())
.putShort("Damage", (short) 0)
.putString("Name", "minecraft:writable_book");
List<NbtMap> pages = new ArrayList<>(hasBookPages ? pageCount : 1);
if (hasBookPages && WritableBookItem.makeSureTagIsValid(book.getTag())) {
ListTag listTag = book.getTag().getList("pages", 8);
for (int i = 0; i < listTag.size(); i++) {
String page = listTag.getString(i);
NbtMapBuilder pageBuilder = NbtMap.builder()
.putString("photoname", "")
.putString("text", page);
pages.add(pageBuilder.build());
}
} else {
// Empty page
NbtMapBuilder pageBuilder = NbtMap.builder()
.putString("photoname", "")
.putString("text", "");
pages.add(pageBuilder.build());
}
bookTag.putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, pages).build());
lecternTag.putCompound("book", bookTag.build());
NbtMap blockEntityTag = lecternTag.build();
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z));
}
@Override
public boolean hasPermission(GeyserSession session, String permission) {
ServerPlayer player = getPlayer(session);
return Permissions.check(player, permission);
}
@Override
public GameMode getDefaultGameMode(GeyserSession session) {
return GameMode.byId(server.getDefaultGameType().getId());
}
@NonNull
@Override
public CompletableFuture<com.github.steveice10.opennbt.tag.builtin.CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) {
CompletableFuture<com.github.steveice10.opennbt.tag.builtin.CompoundTag> future = new CompletableFuture<>();
server.execute(() -> {
ServerPlayer player = getPlayer(session);
if (player == null) {
future.complete(null);
return;
}
BlockPos pos = new BlockPos(x, y, z);
// Don't create a new block entity if invalid
//noinspection resource - level() is just a getter
BlockEntity blockEntity = player.level().getChunkAt(pos).getBlockEntity(pos);
if (blockEntity instanceof BannerBlockEntity banner) {
// Potentially exposes other NBT data? But we need to get the NBT data for the banner patterns *and*
// the banner might have a custom name, both of which a Java client knows and caches
ItemStack itemStack = banner.getItem();
var tag = OpenNbtTagVisitor.convert("", itemStack.getOrCreateTag());
future.complete(tag);
return;
}
future.complete(null);
});
return future;
}
private ServerPlayer getPlayer(GeyserSession session) {
return server.getPlayerList().getPlayer(session.getPlayerEntity().getUuid());
}
// Future considerations: option to clone; would affect arrays
private static class OpenNbtTagVisitor implements TagVisitor {
private String currentKey;
private final com.github.steveice10.opennbt.tag.builtin.CompoundTag root;
private com.github.steveice10.opennbt.tag.builtin.Tag currentTag;
OpenNbtTagVisitor(String key) {
root = new com.github.steveice10.opennbt.tag.builtin.CompoundTag(key);
}
@Override
public void visitString(StringTag stringTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.StringTag(currentKey, stringTag.getAsString());
}
@Override
public void visitByte(ByteTag byteTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.ByteTag(currentKey, byteTag.getAsByte());
}
@Override
public void visitShort(ShortTag shortTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.ShortTag(currentKey, shortTag.getAsShort());
}
@Override
public void visitInt(IntTag intTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.IntTag(currentKey, intTag.getAsInt());
}
@Override
public void visitLong(LongTag longTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.LongTag(currentKey, longTag.getAsLong());
}
@Override
public void visitFloat(FloatTag floatTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.FloatTag(currentKey, floatTag.getAsFloat());
}
@Override
public void visitDouble(DoubleTag doubleTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.DoubleTag(currentKey, doubleTag.getAsDouble());
}
@Override
public void visitByteArray(ByteArrayTag byteArrayTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.ByteArrayTag(currentKey, byteArrayTag.getAsByteArray());
}
@Override
public void visitIntArray(IntArrayTag intArrayTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.IntArrayTag(currentKey, intArrayTag.getAsIntArray());
}
@Override
public void visitLongArray(LongArrayTag longArrayTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.LongArrayTag(currentKey, longArrayTag.getAsLongArray());
}
@Override
public void visitList(ListTag listTag) {
var newList = new com.github.steveice10.opennbt.tag.builtin.ListTag(currentKey);
for (Tag tag : listTag) {
currentKey = "";
tag.accept(this);
newList.add(currentTag);
}
currentTag = newList;
}
@Override
public void visitCompound(@NonNull CompoundTag compoundTag) {
currentTag = convert(currentKey, compoundTag);
}
private static com.github.steveice10.opennbt.tag.builtin.CompoundTag convert(String name, CompoundTag compoundTag) {
OpenNbtTagVisitor visitor = new OpenNbtTagVisitor(name);
for (String key : compoundTag.getAllKeys()) {
visitor.currentKey = key;
Tag tag = Objects.requireNonNull(compoundTag.get(key));
tag.accept(visitor);
visitor.root.put(visitor.currentTag);
}
return visitor.root;
}
@Override
public void visitEnd(@NonNull EndTag endTag) {
}
}
}

View file

@ -1,15 +0,0 @@
{
"required": true,
"package": "org.geysermc.geyser.platform.fabric.mixin",
"compatibilityLevel": "JAVA_16",
"refmap": "geyser-fabric-refmap.json",
"client": [
"client.IntegratedServerMixin"
],
"server": [
"server.MinecraftDedicatedServerMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,22 @@
architectury {
common("neoforge", "fabric")
}
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)
// Only here to suppress "unknown enum constant EnvType.CLIENT" warnings. DO NOT USE!
compileOnly(libs.fabric.loader)
}

View file

@ -0,0 +1,70 @@
plugins {
application
}
architectury {
platformSetupLoomIde()
fabric()
}
val includeTransitive: Configuration = configurations.getByName("includeTransitive")
dependencies {
modImplementation(libs.fabric.loader)
modApi(libs.fabric.api)
api(project(":mod", configuration = "namedElements"))
shadow(project(path = ":mod", configuration = "transformProductionFabric")) {
isTransitive = false
}
shadow(projects.core) { isTransitive = false }
includeTransitive(projects.core)
// These are NOT transitively included, and instead shadowed + relocated.
// Avoids fabric complaining about non-SemVer versioning
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
shadow(libs.erosion.common) { isTransitive = false }
// Let's shade in our own api/common module
shadow(projects.api) { isTransitive = false }
shadow(projects.common) { isTransitive = false }
// Permissions
modImplementation(libs.fabric.permissions)
include(libs.fabric.permissions)
}
application {
mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain")
}
relocate("org.cloudburstmc.netty")
relocate("org.cloudburstmc.protocol")
relocate("com.github.steveice10.mc.auth")
tasks {
remapJar {
archiveBaseName.set("Geyser-Fabric")
}
remapModrinthJar {
archiveBaseName.set("geyser-fabric")
}
}
modrinth {
loaders.add("fabric")
uploadFile.set(tasks.getByPath("remapModrinthJar"))
dependencies {
required.project("fabric-api")
}
}

View file

@ -0,0 +1 @@
loom.platform=fabric

View file

@ -0,0 +1,89 @@
/*
* 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.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;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
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.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInitializer {
public GeyserFabricBootstrap() {
super(new GeyserFabricPlatform());
}
@Override
public void onInitialize() {
if (isServer()) {
// Set as an event, so we can get the proper IP and port if needed
ServerLifecycleEvents.SERVER_STARTED.register((server) -> {
this.setServer(server);
onGeyserEnable();
});
} else {
ClientLifecycleEvents.CLIENT_STOPPING.register(($)-> {
onGeyserShutdown();
});
}
// These are only registered once
ServerLifecycleEvents.SERVER_STOPPING.register((server) -> {
if (isServer()) {
onGeyserShutdown();
} else {
onGeyserDisable();
}
});
ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserModUpdateListener.onPlayReady(handler.getPlayer()));
this.onGeyserInitialize();
}
@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);
}
}

View file

@ -0,0 +1,99 @@
/*
* 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.fabric;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.server.MinecraftServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
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.Path;
import java.util.Optional;
public class GeyserFabricPlatform implements GeyserModPlatform {
private final ModContainer mod;
public GeyserFabricPlatform() {
this.mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow();
}
@Override
public @NonNull PlatformType platformType() {
return PlatformType.FABRIC;
}
@Override
public @NonNull String configPath() {
return "Geyser-Fabric";
}
@Override
public @NonNull Path dataFolder(@NonNull String modId) {
return FabricLoader.getInstance().getConfigDir().resolve(modId);
}
@Override
public @NonNull BootstrapDumpInfo dumpInfo(@NonNull MinecraftServer server) {
return new GeyserFabricDumpInfo(server);
}
@Override
public boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap) {
Optional<ModContainer> floodgate = FabricLoader.getInstance().getModContainer("floodgate");
if (floodgate.isPresent()) {
Path floodgateDataFolder = FabricLoader.getInstance().getConfigDir().resolve("floodgate");
bootstrap.getGeyserConfig().loadFloodgate(bootstrap, floodgateDataFolder);
return true;
}
return false;
}
@Override
public @Nullable InputStream resolveResource(@NonNull String resource) {
// We need to handle this differently, because Fabric shares the classloader across multiple mods
Path path = this.mod.findPath(resource).orElse(null);
if (path == null) {
return null;
}
try {
return path.getFileSystem()
.provider()
.newInputStream(path);
} catch (IOException e) {
return null;
}
}
}

View file

@ -9,23 +9,22 @@
], ],
"contact": { "contact": {
"website": "${url}", "website": "${url}",
"repo": "https://github.com/GeyserMC/Geyser-Fabric" "repo": "https://github.com/GeyserMC/Geyser"
}, },
"license": "MIT", "license": "MIT",
"icon": "assets/geyser-fabric/icon.png", "icon": "assets/geyser/icon.png",
"environment": "*", "environment": "*",
"entrypoints": { "entrypoints": {
"main": [ "main": [
"org.geysermc.geyser.platform.fabric.GeyserFabricMod" "org.geysermc.geyser.platform.fabric.GeyserFabricBootstrap"
] ]
}, },
"mixins": [ "mixins": [
"geyser-fabric.mixins.json" "geyser.mixins.json"
], ],
"depends": { "depends": {
"fabricloader": ">=0.15.2", "fabricloader": ">=0.15.11",
"fabric": "*", "fabric": "*",
"minecraft": ">=1.20.4", "minecraft": ">=1.21"
"fabric-permissions-api-v0": "*"
} }
} }

View file

@ -0,0 +1,59 @@
plugins {
application
}
// 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()
}
val includeTransitive: Configuration = configurations.getByName("includeTransitive")
dependencies {
// See https://github.com/google/guava/issues/6618
modules {
module("com.google.guava:listenablefuture") {
replacedBy("com.google.guava:guava", "listenablefuture is part of guava")
}
}
neoForge(libs.neoforge.minecraft)
api(project(":mod", configuration = "namedElements"))
shadow(project(path = ":mod", configuration = "transformProductionNeoForge")) {
isTransitive = false
}
shadow(projects.core) { isTransitive = false }
// Let's shade in our own api
shadow(projects.api) { isTransitive = false }
// cannot be shaded, since neoforge will complain if floodgate-neoforge tries to provide this
include(projects.common)
// Include all transitive deps of core via JiJ
includeTransitive(projects.core)
}
application {
mainClass.set("org.geysermc.geyser.platform.forge.GeyserNeoForgeMain")
}
tasks {
remapJar {
archiveBaseName.set("Geyser-NeoForge")
}
remapModrinthJar {
archiveBaseName.set("geyser-neoforge")
}
}
modrinth {
loaders.add("neoforge")
uploadFile.set(tasks.getByPath("remapModrinthJar"))
}

View file

@ -0,0 +1 @@
loom.platform=neoforge

View file

@ -0,0 +1,99 @@
/*
* 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.world.entity.player.Player;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.loading.FMLLoader;
import net.neoforged.neoforge.common.NeoForge;
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 org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
@Mod(ModConstants.MOD_ID)
public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
private final GeyserNeoForgePermissionHandler permissionHandler = new GeyserNeoForgePermissionHandler();
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
NeoForge.EVENT_BUS.addListener(this::onServerStarted);
} else {
NeoForge.EVENT_BUS.addListener(this::onClientStopping);
}
NeoForge.EVENT_BUS.addListener(this::onServerStopping);
NeoForge.EVENT_BUS.addListener(this::onPlayerJoin);
NeoForge.EVENT_BUS.addListener(this.permissionHandler::onPermissionGather);
this.onGeyserInitialize();
}
private void onServerStarted(ServerStartedEvent event) {
this.setServer(event.getServer());
this.onGeyserEnable();
}
private void onServerStopping(ServerStoppingEvent event) {
if (isServer()) {
this.onGeyserShutdown();
} else {
this.onGeyserDisable();
}
}
private void onClientStopping(GameShuttingDownEvent ignored) {
this.onGeyserShutdown();
}
private void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) {
GeyserModUpdateListener.onPlayReady(event.getEntity());
}
@Override
public boolean isServer() {
return FMLLoader.getDist().isDedicatedServer();
}
@Override
public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
return this.permissionHandler.hasPermission(source, permissionNode);
}
@Override
public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
return this.permissionHandler.hasPermission(source, permissionNode, permissionLevel);
}
}

View file

@ -0,0 +1,84 @@
/*
* 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 lombok.AllArgsConstructor;
import lombok.Getter;
import net.minecraft.server.MinecraftServer;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.fml.ModList;
import net.neoforged.fml.loading.FMLLoader;
import net.neoforged.neoforgespi.language.IModInfo;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.text.AsteriskSerializer;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@Getter
public class GeyserNeoForgeDumpInfo extends BootstrapDumpInfo {
private final String platformName;
private final String platformVersion;
private final String minecraftVersion;
private final Dist dist;
@AsteriskSerializer.Asterisk(isIp = true)
private final String serverIP;
private final int serverPort;
private final boolean onlineMode;
private final List<ModInfo> mods;
public GeyserNeoForgeDumpInfo(MinecraftServer server) {
this.platformName = FMLLoader.launcherHandlerName();
this.platformVersion = FMLLoader.versionInfo().neoForgeVersion();
this.minecraftVersion = FMLLoader.versionInfo().mcVersion();
this.dist = FMLLoader.getDist();
this.serverIP = server.getLocalIp() == null ? "unknown" : server.getLocalIp();
this.serverPort = server.getPort();
this.onlineMode = server.usesAuthentication();
this.mods = new ArrayList<>();
for (IModInfo mod : ModList.get().getMods()) {
this.mods.add(new ModInfo(
ModList.get().isLoaded(mod.getModId()),
mod.getModId(),
mod.getVersion().toString(),
mod.getModURL().map(URL::toString).orElse("")
));
}
}
@Getter
@AllArgsConstructor
public static class ModInfo {
public boolean enabled;
public String name;
public String version;
public String url;
}
}

View file

@ -0,0 +1,45 @@
/*
* 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 org.geysermc.geyser.GeyserMain;
public class GeyserNeoForgeMain extends GeyserMain {
public static void main(String[] args) {
new GeyserNeoForgeMain().displayMessage();
}
@Override
public String getPluginType() {
return "NeoForge";
}
@Override
public String getPluginFolder() {
return "mods";
}
}

View file

@ -0,0 +1,149 @@
/*
* 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<PermissionNode> 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<String, PermissionNode<Boolean>> permissionNodes = new HashMap<>();
public void onPermissionGather(PermissionGatherEvent.Nodes event) {
this.registerNode(Constants.UPDATE_PERMISSION, event);
GeyserCommandManager commandManager = GeyserImpl.getInstance().commandManager();
for (Map.Entry<String, Command> 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<String, Command> commands : commandManager.extensionCommands().values()) {
for (Map.Entry<String, Command> 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<Boolean> 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<Boolean> 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<Boolean> createNode(String node) {
// The typical constructors in PermissionNode require a
// mod id, which means our permission nodes end up becoming
// geyser_neoforge.<node> instead of just <node>. We work around
// this by using reflection to access the constructor that
// doesn't require a mod id or ResourceLocation.
try {
return (PermissionNode<Boolean>) PERMISSION_NODE_CONSTRUCTOR.newInstance(
node,
PermissionTypes.BOOLEAN,
(PermissionNode.PermissionResolver<Boolean>) (player, playerUUID, context) -> false,
new PermissionDynamicContextKey[0]
);
} catch (Exception e) {
throw new RuntimeException("Unable to create permission node " + node, e);
}
}
}

View file

@ -0,0 +1,91 @@
/*
* 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.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.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;
}
@Override
public @NonNull String configPath() {
return "Geyser-NeoForge";
}
@Override
public @NonNull Path dataFolder(@NonNull String modId) {
return FMLPaths.CONFIGDIR.get().resolve(modId);
}
@Override
public @NonNull BootstrapDumpInfo dumpInfo(@NonNull MinecraftServer server) {
return new GeyserNeoForgeDumpInfo(server);
}
@Override
public boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap) {
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) {
try {
Path path = container.getModInfo().getOwningFile().getFile().findResource(resource);
return Files.newInputStream(path);
} catch (IOException e) {
return null;
}
}
}

View file

@ -23,11 +23,8 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.item.type; package org.geysermc.geyser.platform.neoforge;
// If blocks are implemented, then this class is not needed. public class ModConstants {
public class FlowerItem extends BlockItem { public static final String MOD_ID = "geyser_neoforge";
public FlowerItem(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
} }

View file

@ -0,0 +1,25 @@
modLoader="javafml"
loaderVersion="[1,)"
license="MIT"
[[mods]]
modId="geyser_neoforge"
version="${version}"
displayName="Geyser"
displayURL="https://geysermc.org/"
logoFile= "../assets/geyser/icon.png"
authors="GeyserMC"
description="${description}"
[[mixins]]
config = "geyser.mixins.json"
[[dependencies.geyser_neoforge]]
modId="neoforge"
type="required"
versionRange="[21.0.0-beta,)"
ordering="NONE"
side="BOTH"
[[dependencies.geyser_neoforge]]
modId="minecraft"
type="required"
versionRange="[1.21,)"
ordering="NONE"
side="BOTH"

View file

@ -0,0 +1,43 @@
/*
* 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.mod;
import io.netty.channel.ChannelFuture;
import java.util.List;
/**
* Represents a getter to the server channels in the connection listener class.
*/
public interface GeyserChannelGetter {
/**
* Returns the channels.
*
* @return The channels.
*/
List<ChannelFuture> geyser$getChannels();
}

View file

@ -23,20 +23,17 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.platform.fabric; package org.geysermc.geyser.platform.mod;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.fabricmc.api.EnvType; import lombok.Getter;
import net.fabricmc.api.ModInitializer; import lombok.RequiredArgsConstructor;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import lombok.Setter;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands; import net.minecraft.commands.Commands;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import org.apache.logging.log4j.LogManager; import net.minecraft.world.entity.player.Player;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserBootstrap;
@ -44,7 +41,6 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.configuration.GeyserConfiguration;
@ -52,95 +48,66 @@ import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.fabric.command.GeyserFabricCommandExecutor; import org.geysermc.geyser.platform.mod.command.GeyserModCommandExecutor;
import org.geysermc.geyser.platform.fabric.world.GeyserFabricWorldManager; import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.FileUtils;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.SocketAddress;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { @RequiredArgsConstructor
private static GeyserFabricMod instance; public abstract class GeyserModBootstrap implements GeyserBootstrap {
private boolean reloading; @Getter
private static GeyserModBootstrap instance;
private final GeyserModPlatform platform;
private GeyserImpl geyser; private GeyserImpl geyser;
private ModContainer mod;
private Path dataFolder; private Path dataFolder;
@Setter
private MinecraftServer server; private MinecraftServer server;
private GeyserCommandManager geyserCommandManager; private GeyserCommandManager geyserCommandManager;
private GeyserFabricConfiguration geyserConfig; private GeyserModConfiguration geyserConfig;
private GeyserFabricLogger geyserLogger; private GeyserModInjector geyserInjector;
private final GeyserModLogger geyserLogger = new GeyserModLogger();
private IGeyserPingPassthrough geyserPingPassthrough; private IGeyserPingPassthrough geyserPingPassthrough;
private WorldManager geyserWorldManager; private WorldManager geyserWorldManager;
@Override @Override
public void onInitialize() { public void onGeyserInitialize() {
instance = this; instance = this;
mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow(); dataFolder = this.platform.dataFolder(this.platform.configPath());
this.onEnable();
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) {
// Set as an event so we can get the proper IP and port if needed
ServerLifecycleEvents.SERVER_STARTED.register(this::startGeyser);
}
}
@Override
public void onEnable() {
dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric");
if (!dataFolder.toFile().exists()) {
//noinspection ResultOfMethodCallIgnored
dataFolder.toFile().mkdir();
}
// Init dataFolder first as local language overrides call getConfigFolder()
GeyserLocale.init(this); GeyserLocale.init(this);
if (!loadConfig()) {
try {
File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml",
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class);
} catch (IOException ex) {
LogManager.getLogger("geyser-fabric").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
return; return;
} }
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
this.geyserLogger = new GeyserFabricLogger(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(this.platform.platformType(), this);
this.geyser = GeyserImpl.load(PlatformType.FABRIC, this); // Create command manager here, since the permission handler on neo needs it
this.geyserCommandManager = new GeyserCommandManager(geyser);
if (server == null) { this.geyserCommandManager.init();
// Server has yet to start
// Register onDisable so players are properly kicked
ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable());
ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler));
} else {
// Server has started and this is a reload
startGeyser(this.server);
reloading = false;
}
} }
/** public void onGeyserEnable() {
* Initialize core Geyser. if (GeyserImpl.getInstance().isReloading()) {
* A function, as it needs to be called in different places depending on if Geyser is being reloaded or not. if (!loadConfig()) {
* return;
* @param server The minecraft server. }
*/ this.geyserLogger.setDebug(geyserConfig.isDebugMode());
public void startGeyser(MinecraftServer server) { GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.server = server; }
GeyserImpl.start(); GeyserImpl.start();
@ -150,20 +117,29 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger); this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger);
} }
this.geyserCommandManager = new GeyserCommandManager(geyser); // No need to re-register commands, or try to re-inject
this.geyserCommandManager.init(); if (GeyserImpl.getInstance().isReloading()) {
return;
}
this.geyserWorldManager = new GeyserFabricWorldManager(server); this.geyserWorldManager = new GeyserModWorldManager(server);
// We want to do this late in the server startup process to allow other mods
// To do their job injecting, then connect into *that*
this.geyserInjector = new GeyserModInjector(server, this.platform);
if (isServer()) {
this.geyserInjector.initializeLocalChannel(this);
}
// Start command building // Start command building
// Set just "geyser" as the help command // Set just "geyser" as the help command
GeyserFabricCommandExecutor helpExecutor = new GeyserFabricCommandExecutor(geyser, GeyserModCommandExecutor helpExecutor = new GeyserModCommandExecutor(geyser,
(GeyserCommand) geyser.commandManager().getCommands().get("help")); (GeyserCommand) geyser.commandManager().getCommands().get("help"));
LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal("geyser").executes(helpExecutor); LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal("geyser").executes(helpExecutor);
// Register all subcommands as valid // Register all subcommands as valid
for (Map.Entry<String, Command> command : geyser.commandManager().getCommands().entrySet()) { for (Map.Entry<String, Command> command : geyser.commandManager().getCommands().entrySet()) {
GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(geyser, (GeyserCommand) command.getValue()); GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
builder.then(Commands.literal(command.getKey()) builder.then(Commands.literal(command.getKey())
.executes(executor) .executes(executor)
// Could also test for Bedrock but depending on when this is called it may backfire // Could also test for Bedrock but depending on when this is called it may backfire
@ -183,12 +159,12 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
} }
// Register help command for just "/<extensionId>" // Register help command for just "/<extensionId>"
GeyserFabricCommandExecutor extensionHelpExecutor = new GeyserFabricCommandExecutor(geyser, GeyserModCommandExecutor extensionHelpExecutor = new GeyserModCommandExecutor(geyser,
(GeyserCommand) extensionCommands.get("help")); (GeyserCommand) extensionCommands.get("help"));
LiteralArgumentBuilder<CommandSourceStack> extCmdBuilder = Commands.literal(extensionMapEntry.getKey().description().id()).executes(extensionHelpExecutor); LiteralArgumentBuilder<CommandSourceStack> extCmdBuilder = Commands.literal(extensionMapEntry.getKey().description().id()).executes(extensionHelpExecutor);
for (Map.Entry<String, Command> command : extensionCommands.entrySet()) { for (Map.Entry<String, Command> command : extensionCommands.entrySet()) {
GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(geyser, (GeyserCommand) command.getValue()); GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
extCmdBuilder.then(Commands.literal(command.getKey()) extCmdBuilder.then(Commands.literal(command.getKey())
.executes(executor) .executes(executor)
.requires(executor::testPermission) .requires(executor::testPermission)
@ -201,18 +177,26 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
} }
@Override @Override
public void onDisable() { public void onGeyserDisable() {
if (geyser != null) {
geyser.disable();
}
}
@Override
public void onGeyserShutdown() {
if (geyser != null) { if (geyser != null) {
geyser.shutdown(); geyser.shutdown();
geyser = null; geyser = null;
} }
if (!reloading) { if (geyserInjector != null) {
geyserInjector.shutdown();
this.server = null; this.server = null;
} }
} }
@Override @Override
public GeyserConfiguration getGeyserConfig() { public GeyserModConfiguration getGeyserConfig() {
return geyserConfig; return geyserConfig;
} }
@ -243,7 +227,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
@Override @Override
public BootstrapDumpInfo getDumpInfo() { public BootstrapDumpInfo getDumpInfo() {
return new GeyserFabricDumpInfo(server); return this.platform.dumpInfo(this.server);
} }
@Override @Override
@ -259,43 +243,53 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
} }
@Override @Override
public int getServerPort() { public SocketAddress getSocketAddress() {
return ((GeyserServerPortGetter) server).geyser$getServerPort(); return this.geyserInjector.getServerSocketAddress();
} }
@Override @Override
public boolean testFloodgatePluginPresent() { public int getServerPort() {
Optional<ModContainer> floodgate = FabricLoader.getInstance().getModContainer("floodgate"); if (isServer()) {
if (floodgate.isPresent()) { return ((GeyserServerPortGetter) server).geyser$getServerPort();
geyserConfig.loadFloodgate(this, floodgate.orElse(null)); } else {
return true; // Set in the IntegratedServerMixin
return geyserConfig.getRemote().port();
} }
return false; }
public abstract boolean isServer();
@Override
public boolean testFloodgatePluginPresent() {
return this.platform.testFloodgatePluginPresent(this);
} }
@Nullable @Nullable
@Override @Override
public InputStream getResourceOrNull(String resource) { public InputStream getResourceOrNull(String resource) {
// We need to handle this differently, because Fabric shares the classloader across multiple mods return this.platform.resolveResource(resource);
Path path = this.mod.findPath(resource).orElse(null); }
if (path == null) {
return null;
}
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 { try {
return path.getFileSystem() if (!dataFolder.toFile().exists()) {
.provider() //noinspection ResultOfMethodCallIgnored
.newInputStream(path); dataFolder.toFile().mkdir();
} catch (IOException e) { }
return null;
File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml",
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserModConfiguration.class);
return true;
} catch (IOException ex) {
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
return false;
} }
} }
public void setReloading(boolean reloading) {
this.reloading = reloading;
}
public static GeyserFabricMod getInstance() {
return instance;
}
} }

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2019-2021 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;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import net.minecraft.network.protocol.login.ClientboundGameProfilePacket;
import net.minecraft.network.protocol.login.ClientboundLoginCompressionPacket;
/**
* Disables the compression packet (and the compression handlers from being added to the pipeline) for Geyser clients
* that won't be receiving the data over the network.
* <p>
* As of 1.8 - 1.17.1, compression is enabled in the Netty pipeline by adding a listener after a packet is written.
* If we simply "cancel" or don't forward the packet, then the listener is never called.
*/
public class GeyserModCompressionDisabler extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
Class<?> msgClass = msg.getClass();
// Don't let any compression packet get through
if (!ClientboundLoginCompressionPacket.class.isAssignableFrom(msgClass)) {
if (ClientboundGameProfilePacket.class.isAssignableFrom(msgClass)) {
// We're past the point that a compression packet can be sent, so we can safely yeet ourselves away
ctx.channel().pipeline().remove(this);
}
super.write(ctx, msg, promise);
}
}
}

View file

@ -23,23 +23,20 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.platform.fabric; package org.geysermc.geyser.platform.mod;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import org.geysermc.geyser.FloodgateKeyLoader; import org.geysermc.geyser.FloodgateKeyLoader;
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
import java.nio.file.Path; import java.nio.file.Path;
public class GeyserFabricConfiguration extends GeyserJacksonConfiguration { public class GeyserModConfiguration extends GeyserJacksonConfiguration {
@JsonIgnore @JsonIgnore
private Path floodgateKeyPath; private Path floodgateKeyPath;
public void loadFloodgate(GeyserFabricMod geyser, ModContainer floodgate) { public void loadFloodgate(GeyserModBootstrap geyser, Path floodgateDataFolder) {
Path geyserDataFolder = geyser.getConfigFolder(); Path geyserDataFolder = geyser.getConfigFolder();
Path floodgateDataFolder = floodgate != null ? FabricLoader.getInstance().getConfigDir().resolve("floodgate") : null;
floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, geyser.getGeyserLogger()); floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, geyser.getGeyserLogger());
} }

View file

@ -0,0 +1,159 @@
/*
* 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.mod;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.local.LocalAddress;
import io.netty.util.concurrent.DefaultThreadFactory;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerConnectionListener;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.network.netty.GeyserInjector;
import org.geysermc.geyser.network.netty.LocalServerChannelWrapper;
import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
public class GeyserModInjector extends GeyserInjector {
private final MinecraftServer server;
private final GeyserModPlatform platform;
private DefaultEventLoopGroup eventLoopGroup;
/**
* Used to uninject ourselves on shutdown.
*/
private List<ChannelFuture> allServerChannels;
public GeyserModInjector(MinecraftServer server, GeyserModPlatform platform) {
this.server = server;
this.platform = platform;
}
@Override
protected void initializeLocalChannel0(GeyserBootstrap bootstrap) throws Exception {
ServerConnectionListener connection = this.server.getConnection();
// Find the channel that Minecraft uses to listen to connections
ChannelFuture listeningChannel = null;
this.allServerChannels = ((GeyserChannelGetter) connection).geyser$getChannels();
for (ChannelFuture o : allServerChannels) {
listeningChannel = o;
break;
}
if (listeningChannel == null) {
throw new RuntimeException("Unable to find listening channel!");
}
// Making this a function prevents childHandler from being treated as a non-final variable
ChannelInitializer<Channel> childHandler = getChildHandler(bootstrap, listeningChannel);
// This method is what initializes the connection in Java Edition, after Netty is all set.
Method initChannel = childHandler.getClass().getDeclaredMethod("initChannel", Channel.class);
initChannel.setAccessible(true);
// Separate variable so we can shut it down later
eventLoopGroup = new DefaultEventLoopGroup(0, new DefaultThreadFactory("Geyser " + this.platform.platformType().platformName() + " connection thread", Thread.MAX_PRIORITY));
ChannelFuture channelFuture = (new ServerBootstrap()
.channel(LocalServerChannelWrapper.class)
.childHandler(new ChannelInitializer<>() {
@Override
protected void initChannel(@NonNull Channel ch) throws Exception {
initChannel.invoke(childHandler, ch);
int index = ch.pipeline().names().indexOf("encoder");
String baseName = index != -1 ? "encoder" : "outbound_config";
if (bootstrap.getGeyserConfig().isDisableCompression()) {
ch.pipeline().addAfter(baseName, "geyser-compression-disabler", new GeyserModCompressionDisabler());
}
}
})
// Set to MAX_PRIORITY as MultithreadEventLoopGroup#newDefaultThreadFactory which DefaultEventLoopGroup implements does by default
.group(eventLoopGroup)
.localAddress(LocalAddress.ANY))
.bind()
.syncUninterruptibly();
// We don't need to add to the list, but plugins like ProtocolSupport and ProtocolLib that add to the main pipeline
// will work when we add to the list.
allServerChannels.add(channelFuture);
this.localChannel = channelFuture;
this.serverSocketAddress = channelFuture.channel().localAddress();
}
@SuppressWarnings("unchecked")
private ChannelInitializer<Channel> getChildHandler(GeyserBootstrap bootstrap, ChannelFuture listeningChannel) {
List<String> names = listeningChannel.channel().pipeline().names();
ChannelInitializer<Channel> childHandler = null;
for (String name : names) {
ChannelHandler handler = listeningChannel.channel().pipeline().get(name);
try {
Field childHandlerField = handler.getClass().getDeclaredField("childHandler");
childHandlerField.setAccessible(true);
childHandler = (ChannelInitializer<Channel>) childHandlerField.get(handler);
break;
} catch (Exception e) {
if (bootstrap.getGeyserConfig().isDebugMode()) {
bootstrap.getGeyserLogger().debug("The handler " + name + " isn't a ChannelInitializer. THIS ERROR IS SAFE TO IGNORE!");
e.printStackTrace();
}
}
}
if (childHandler == null) {
throw new RuntimeException();
}
return childHandler;
}
@Override
public void shutdown() {
if (this.allServerChannels != null) {
this.allServerChannels.remove(this.localChannel);
this.allServerChannels = null;
}
if (eventLoopGroup != null) {
try {
eventLoopGroup.shutdownGracefully().sync();
eventLoopGroup = null;
} catch (Exception e) {
GeyserImpl.getInstance().getLogger().error("Unable to shut down injector! " + e.getMessage());
e.printStackTrace();
}
}
super.shutdown();
}
}

View file

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.platform.fabric; package org.geysermc.geyser.platform.mod;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
@ -32,15 +32,11 @@ import org.apache.logging.log4j.Logger;
import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.ChatColor;
public class GeyserFabricLogger implements GeyserLogger { public class GeyserModLogger implements GeyserLogger {
private final Logger logger = LogManager.getLogger("geyser-fabric"); private final Logger logger = LogManager.getLogger("geyser");
private boolean debug; private boolean debug;
public GeyserFabricLogger(boolean isDebug) {
debug = isDebug;
}
@Override @Override
public void severe(String message) { public void severe(String message) {
logger.fatal(message); logger.fatal(message);
@ -73,7 +69,7 @@ public class GeyserFabricLogger implements GeyserLogger {
@Override @Override
public void sendMessage(Component message) { public void sendMessage(Component message) {
// As of Java Edition 1.19.2, Fabric's console doesn't natively support legacy format // As of Java Edition 1.19.2, Minecraft's console doesn't natively support legacy format
String flattened = LegacyComponentSerializer.legacySection().serialize(message); String flattened = LegacyComponentSerializer.legacySection().serialize(message);
// Add the reset at the end, or else format will persist... forever. // Add the reset at the end, or else format will persist... forever.
// https://cdn.discordapp.com/attachments/573909525132738590/1033904509170225242/unknown.png // https://cdn.discordapp.com/attachments/573909525132738590/1033904509170225242/unknown.png

View file

@ -23,21 +23,22 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.platform.fabric; package org.geysermc.geyser.platform.mod;
import me.lucko.fabric.api.permissions.v0.Permissions; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.network.ServerGamePacketListenerImpl; import net.minecraft.world.entity.player.Player;
import org.geysermc.geyser.Constants; import org.geysermc.geyser.Constants;
import org.geysermc.geyser.platform.fabric.command.FabricCommandSender; import org.geysermc.geyser.platform.mod.command.ModCommandSender;
import org.geysermc.geyser.util.VersionCheckUtils; import org.geysermc.geyser.util.VersionCheckUtils;
public final class GeyserFabricUpdateListener { public final class GeyserModUpdateListener {
public static void onPlayReady(ServerGamePacketListenerImpl handler) { public static void onPlayReady(Player player) {
if (Permissions.check(handler.player, Constants.UPDATE_PERMISSION, 2)) { CommandSourceStack stack = player.createCommandSourceStack();
VersionCheckUtils.checkForGeyserUpdate(() -> new FabricCommandSender(handler.player.createCommandSourceStack())); if (GeyserModBootstrap.getInstance().hasPermission(stack, Constants.UPDATE_PERMISSION, 2)) {
VersionCheckUtils.checkForGeyserUpdate(() -> new ModCommandSender(stack));
} }
} }
private GeyserFabricUpdateListener() { private GeyserModUpdateListener() {
} }
} }

View file

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.platform.fabric; package org.geysermc.geyser.platform.mod;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;

View file

@ -23,12 +23,13 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.platform.fabric; package org.geysermc.geyser.platform.mod;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.Connection; import net.minecraft.network.Connection;
import net.minecraft.network.PacketSendListener; import net.minecraft.network.PacketSendListener;
import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.Packet;
@ -39,10 +40,11 @@ import net.minecraft.network.protocol.status.ServerStatusPacketListener;
import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket; import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerStatusPacketListenerImpl; import net.minecraft.server.network.ServerStatusPacketListenerImpl;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.ping.GeyserPingInfo; import org.geysermc.geyser.ping.GeyserPingInfo;
import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.jetbrains.annotations.Nullable;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Objects; import java.util.Objects;
@ -68,7 +70,7 @@ public class ModPingPassthrough implements IGeyserPingPassthrough {
StatusInterceptor connection = new StatusInterceptor(); StatusInterceptor connection = new StatusInterceptor();
ServerStatusPacketListener statusPacketListener = new ServerStatusPacketListenerImpl(status, connection); ServerStatusPacketListener statusPacketListener = new ServerStatusPacketListenerImpl(status, connection);
statusPacketListener.handleStatusRequest(new ServerboundStatusRequestPacket()); statusPacketListener.handleStatusRequest(ServerboundStatusRequestPacket.INSTANCE);
// mods like MiniMOTD (that inject into the above method) have now processed the response // mods like MiniMOTD (that inject into the above method) have now processed the response
status = Objects.requireNonNull(connection.status, "status response"); status = Objects.requireNonNull(connection.status, "status response");
} catch (Exception e) { } catch (Exception e) {
@ -78,7 +80,7 @@ public class ModPingPassthrough implements IGeyserPingPassthrough {
} }
} }
String jsonDescription = net.minecraft.network.chat.Component.Serializer.toJson(status.description()); String jsonDescription = net.minecraft.network.chat.Component.Serializer.toJson(status.description(), RegistryAccess.EMPTY);
String legacyDescription = LEGACY_SERIALIZER.serialize(GSON_SERIALIZER.deserializeOr(jsonDescription, Component.empty())); String legacyDescription = LEGACY_SERIALIZER.serialize(GSON_SERIALIZER.deserializeOr(jsonDescription, Component.empty()));
return new GeyserPingInfo( return new GeyserPingInfo(
@ -100,7 +102,7 @@ public class ModPingPassthrough implements IGeyserPingPassthrough {
} }
@Override @Override
public void send(Packet<?> packet, @Nullable PacketSendListener packetSendListener, boolean bl) { public void send(@NonNull Packet<?> packet, @Nullable PacketSendListener packetSendListener, boolean bl) {
if (packet instanceof ClientboundStatusResponsePacket statusResponse) { if (packet instanceof ClientboundStatusResponsePacket statusResponse) {
status = statusResponse.status(); status = statusResponse.status();
} }

View file

@ -23,32 +23,31 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.platform.fabric.command; package org.geysermc.geyser.platform.mod.command;
import com.mojang.brigadier.Command; import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandExecutor; import org.geysermc.geyser.command.GeyserCommandExecutor;
import org.geysermc.geyser.platform.fabric.GeyserFabricMod; import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import java.util.Collections; import java.util.Collections;
public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implements Command<CommandSourceStack> { public class GeyserModCommandExecutor extends GeyserCommandExecutor implements Command<CommandSourceStack> {
private final GeyserCommand command; private final GeyserCommand command;
public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command) { public GeyserModCommandExecutor(GeyserImpl geyser, GeyserCommand command) {
super(connector, Collections.singletonMap(command.name(), command)); super(geyser, Collections.singletonMap(command.name(), command));
this.command = command; this.command = command;
} }
public boolean testPermission(CommandSourceStack source) { public boolean testPermission(CommandSourceStack source) {
return Permissions.check(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0); return GeyserModBootstrap.getInstance().hasPermission(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0);
} }
@Override @Override
@ -58,15 +57,12 @@ public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implement
public int runWithArgs(CommandContext<CommandSourceStack> context, String args) { public int runWithArgs(CommandContext<CommandSourceStack> context, String args) {
CommandSourceStack source = context.getSource(); CommandSourceStack source = context.getSource();
FabricCommandSender sender = new FabricCommandSender(source); ModCommandSender sender = new ModCommandSender(source);
GeyserSession session = getGeyserSession(sender); GeyserSession session = getGeyserSession(sender);
if (!testPermission(source)) { if (!testPermission(source)) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
return 0; return 0;
} }
if (this.command.name().equals("reload")) {
GeyserFabricMod.getInstance().setReloading(true);
}
if (command.isBedrockOnly() && session == null) { if (command.isBedrockOnly() && session == null) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale())); sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale()));

View file

@ -23,25 +23,26 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.platform.fabric.command; package org.geysermc.geyser.platform.mod.command;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.ChatColor;
import java.util.Objects; import java.util.Objects;
public class FabricCommandSender implements GeyserCommandSource { public class ModCommandSender implements GeyserCommandSource {
private final CommandSourceStack source; private final CommandSourceStack source;
public FabricCommandSender(CommandSourceStack source) { public ModCommandSender(CommandSourceStack source) {
this.source = source; this.source = source;
} }
@ -63,7 +64,7 @@ public class FabricCommandSender implements GeyserCommandSource {
public void sendMessage(net.kyori.adventure.text.Component message) { public void sendMessage(net.kyori.adventure.text.Component message) {
if (source.getEntity() instanceof ServerPlayer player) { if (source.getEntity() instanceof ServerPlayer player) {
String decoded = GsonComponentSerializer.gson().serialize(message); String decoded = GsonComponentSerializer.gson().serialize(message);
player.displayClientMessage(Objects.requireNonNull(Component.Serializer.fromJson(decoded)), false); player.displayClientMessage(Objects.requireNonNull(Component.Serializer.fromJson(decoded, RegistryAccess.EMPTY)), false);
return; return;
} }
GeyserCommandSource.super.sendMessage(message); GeyserCommandSource.super.sendMessage(message);
@ -76,6 +77,6 @@ public class FabricCommandSender implements GeyserCommandSource {
@Override @Override
public boolean hasPermission(String permission) { public boolean hasPermission(String permission) {
return Permissions.check(source, permission, source.getServer().getOperatorUserPermissionLevel()); return GeyserModBootstrap.getInstance().hasPermission(source, permission, source.getServer().getOperatorUserPermissionLevel());
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -23,18 +23,16 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.platform.fabric.mixin.client; package org.geysermc.geyser.platform.mod.mixin.client;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.server.IntegratedServer; import net.minecraft.client.server.IntegratedServer;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.GameType; import net.minecraft.world.level.GameType;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.platform.fabric.GeyserFabricMod; import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.platform.fabric.GeyserServerPortGetter; import org.geysermc.geyser.platform.mod.GeyserServerPortGetter;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
@ -45,7 +43,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Objects; import java.util.Objects;
@Environment(EnvType.CLIENT)
@Mixin(IntegratedServer.class) @Mixin(IntegratedServer.class)
public class IntegratedServerMixin implements GeyserServerPortGetter { public class IntegratedServerMixin implements GeyserServerPortGetter {
@Shadow @Shadow
@ -57,7 +54,10 @@ public class IntegratedServerMixin implements GeyserServerPortGetter {
private void onOpenToLan(GameType gameType, boolean cheatsAllowed, int port, CallbackInfoReturnable<Boolean> cir) { private void onOpenToLan(GameType gameType, boolean cheatsAllowed, int port, CallbackInfoReturnable<Boolean> cir) {
if (cir.getReturnValueZ()) { if (cir.getReturnValueZ()) {
// If the LAN is opened, starts Geyser. // If the LAN is opened, starts Geyser.
GeyserFabricMod.getInstance().startGeyser((MinecraftServer) (Object) this); GeyserModBootstrap instance = GeyserModBootstrap.getInstance();
instance.setServer((MinecraftServer) (Object) this);
instance.getGeyserConfig().getRemote().setPort(port);
instance.onGeyserEnable();
// Ensure player locale has been loaded, in case it's different from Java system language // Ensure player locale has been loaded, in case it's different from Java system language
GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode); GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode);
// Give indication that Geyser is loaded // Give indication that Geyser is loaded

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.platform.fabric.mixin.server; package org.geysermc.geyser.platform.mod.mixin.server;
import com.mojang.datafixers.DataFixer; import com.mojang.datafixers.DataFixer;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
@ -33,14 +33,14 @@ import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.level.progress.ChunkProgressListenerFactory; import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
import net.minecraft.server.packs.repository.PackRepository; import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.LevelStorageSource;
import org.geysermc.geyser.platform.fabric.GeyserServerPortGetter; import org.geysermc.geyser.platform.mod.GeyserServerPortGetter;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import java.net.Proxy; import java.net.Proxy;
@Mixin(DedicatedServer.class) @Mixin(DedicatedServer.class)
public abstract class MinecraftDedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter { public abstract class DedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter {
public MinecraftDedicatedServerMixin(Thread thread, LevelStorageSource.LevelStorageAccess levelStorageAccess, PackRepository packRepository, WorldStem worldStem, Proxy proxy, DataFixer dataFixer, Services services, ChunkProgressListenerFactory chunkProgressListenerFactory) { public DedicatedServerMixin(Thread thread, LevelStorageSource.LevelStorageAccess levelStorageAccess, PackRepository packRepository, WorldStem worldStem, Proxy proxy, DataFixer dataFixer, Services services, ChunkProgressListenerFactory chunkProgressListenerFactory) {
super(thread, levelStorageAccess, packRepository, worldStem, proxy, dataFixer, services, chunkProgressListenerFactory); super(thread, levelStorageAccess, packRepository, worldStem, proxy, dataFixer, services, chunkProgressListenerFactory);
} }

View file

@ -0,0 +1,46 @@
/*
* 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.mod.mixin.server;
import io.netty.channel.ChannelFuture;
import net.minecraft.server.network.ServerConnectionListener;
import org.geysermc.geyser.platform.mod.GeyserChannelGetter;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.List;
@Mixin(ServerConnectionListener.class)
public abstract class ServerConnectionListenerMixin implements GeyserChannelGetter {
@Shadow @Final private List<ChannelFuture> channels;
@Override
public List<ChannelFuture> geyser$getChannels() {
return this.channels;
}
}

View file

@ -0,0 +1,92 @@
/*
* 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.mod.platform;
import net.minecraft.server.MinecraftServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import java.io.InputStream;
import java.nio.file.Path;
/**
* An interface which holds common methods that have different
* APIs on their respective mod platforms.
*/
public interface GeyserModPlatform {
/**
* Gets the {@link PlatformType} of the mod platform.
*
* @return the platform type of the mod platform
*/
@NonNull
PlatformType platformType();
/**
* Gets the config path of the mod platform.
*
* @return the config path of the mod platform
*/
@NonNull
String configPath();
/**
* Gets the data folder of the mod platform.
*
* @return the data folder of the mod platform
*/
@NonNull
Path dataFolder(@NonNull String modId);
/**
* Gets the dump info of the mod platform.
*
* @param server the server to get the dump info from
* @return the dump info of the mod platform
*/
@NonNull
BootstrapDumpInfo dumpInfo(@NonNull MinecraftServer server);
/**
* Tests if the Floodgate plugin is present on the mod platform.
*
* @return {@code true} if the Floodgate plugin is present on the mod platform, {@code false} otherwise
*/
boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap);
/**
* Resolves a resource from the mod jar.
*
* @param resource the name of the resource
* @return the input stream of the resource
*/
@Nullable
InputStream resolveResource(@NonNull String resource);
}

View file

@ -0,0 +1,208 @@
/*
* 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.world;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BannerBlockEntity;
import net.minecraft.world.level.block.entity.BannerPatternLayers;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.DecoratedPotBlockEntity;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3i;
import org.geysermc.geyser.level.GeyserWorldManager;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.MinecraftKey;
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public class GeyserModWorldManager extends GeyserWorldManager {
private static final GsonComponentSerializer GSON_SERIALIZER = GsonComponentSerializer.gson();
private final MinecraftServer server;
public GeyserModWorldManager(MinecraftServer server) {
this.server = server;
}
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
// If the protocol version of Geyser and the server are not the
// same, fallback to the chunk cache. May be able to update this
// in the future to use ViaVersion however, like Spigot does.
if (SharedConstants.getCurrentVersion().getProtocolVersion() != GameProtocol.getJavaProtocolVersion()) {
return super.getBlockAt(session, x, y, z);
}
ServerPlayer player = this.getPlayer(session);
if (player == null) {
return 0;
}
Level level = player.level();
if (y < level.getMinBuildHeight()) {
return 0;
}
ChunkAccess chunk = level.getChunkSource().getChunk(x >> 4, z >> 4, ChunkStatus.FULL, false);
if (chunk == null) {
return 0;
}
int worldOffset = level.getMinBuildHeight() >> 4;
int chunkOffset = (y >> 4) - worldOffset;
if (chunkOffset < chunk.getSections().length) {
LevelChunkSection section = chunk.getSections()[chunkOffset];
if (section != null && !section.hasOnlyAir()) {
return Block.getId(section.getBlockState(x & 15, y & 15, z & 15));
}
}
return 0;
}
@Override
public boolean hasOwnChunkCache() {
return SharedConstants.getCurrentVersion().getProtocolVersion() == GameProtocol.getJavaProtocolVersion();
}
@Override
public boolean hasPermission(GeyserSession session, String permission) {
ServerPlayer player = getPlayer(session);
return GeyserModBootstrap.getInstance().hasPermission(player, permission);
}
@Override
public GameMode getDefaultGameMode(GeyserSession session) {
return GameMode.byId(server.getDefaultGameType().getId());
}
@NonNull
@Override
public CompletableFuture<org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents> getPickItemComponents(GeyserSession session, int x, int y, int z, boolean addNbtData) {
CompletableFuture<org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents> future = new CompletableFuture<>();
server.execute(() -> {
ServerPlayer player = getPlayer(session);
if (player == null) {
future.complete(null);
return;
}
BlockPos pos = new BlockPos(x, y, z);
// Don't create a new block entity if invalid
//noinspection resource - level() is just a getter
BlockEntity blockEntity = player.level().getChunkAt(pos).getBlockEntity(pos);
if (blockEntity instanceof BannerBlockEntity banner) {
// Potentially exposes other NBT data? But we need to get the NBT data for the banner patterns *and*
// the banner might have a custom name, both of which a Java client knows and caches
ItemStack itemStack = banner.getItem();
org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents components =
new org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents(new HashMap<>());
components.put(DataComponentType.DAMAGE, itemStack.getDamageValue());
Component customName = itemStack.getComponents().get(DataComponents.CUSTOM_NAME);
if (customName != null) {
components.put(DataComponentType.CUSTOM_NAME, toKyoriComponent(customName));
}
BannerPatternLayers pattern = itemStack.get(DataComponents.BANNER_PATTERNS);
if (pattern != null) {
components.put(DataComponentType.BANNER_PATTERNS, toPatternList(pattern));
}
future.complete(components);
return;
}
future.complete(null);
});
return future;
}
@Override
public void getDecoratedPotData(GeyserSession session, Vector3i pos, Consumer<List<String>> apply) {
server.execute(() -> {
ServerPlayer player = getPlayer(session);
if (player == null) {
return;
}
BlockPos blockPos = new BlockPos(pos.getX(), pos.getY(), pos.getZ());
// Don't create a new block entity if invalid
//noinspection resource - level() is just a getter
BlockEntity blockEntity = player.level().getChunkAt(blockPos).getBlockEntity(blockPos);
if (blockEntity instanceof DecoratedPotBlockEntity pot) {
List<String> sherds = pot.getDecorations().ordered()
.stream().map(item -> BuiltInRegistries.ITEM.getKey(item).toString())
.toList();
apply.accept(sherds);
}
});
}
private ServerPlayer getPlayer(GeyserSession session) {
return server.getPlayerList().getPlayer(session.getPlayerEntity().getUuid());
}
private static net.kyori.adventure.text.Component toKyoriComponent(Component component) {
String json = Component.Serializer.toJson(component, RegistryAccess.EMPTY);
return GSON_SERIALIZER.deserializeOr(json, net.kyori.adventure.text.Component.empty());
}
private static List<BannerPatternLayer> toPatternList(BannerPatternLayers patternLayers) {
return patternLayers.layers().stream()
.map(layer -> {
BannerPatternLayer.BannerPattern pattern = new BannerPatternLayer.BannerPattern(
MinecraftKey.key(layer.pattern().value().assetId().toString()), layer.pattern().value().translationKey()
);
return new BannerPatternLayer(Holder.ofCustom(pattern), layer.color().getId());
})
.toList();
}
}

View file

@ -0,0 +1,18 @@
{
"required": true,
"minVersion": "0.8",
"package": "org.geysermc.geyser.platform.mod.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"server.ServerConnectionListenerMixin"
],
"server": [
"server.DedicatedServerMixin"
],
"client": [
"client.IntegratedServerMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -4,25 +4,27 @@ dependencies {
isTransitive = false isTransitive = false
} }
implementation(libs.erosion.bukkit.nms) {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 21)
}
}
implementation(variantOf(libs.adapters.spigot) { implementation(variantOf(libs.adapters.spigot) {
classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations
}) })
implementation(variantOf(libs.adapters.paper) {
classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations
})
implementation(libs.commodore) implementation(libs.commodore)
implementation(libs.adventure.text.serializer.bungeecord) implementation(libs.adventure.text.serializer.bungeecord)
// Both folia-api and paper-mojangapi only provide Java 17 versions for 1.19 compileOnly(libs.folia.api)
compileOnly(libs.folia.api) { compileOnly(libs.paper.mojangapi)
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) compileOnlyApi(libs.viaversion)
}
}
compileOnly(libs.paper.mojangapi) {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
}
}
} }
platformRelocate("it.unimi.dsi.fastutil") platformRelocate("it.unimi.dsi.fastutil")
@ -41,6 +43,12 @@ application {
} }
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> { tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
// Prevents Paper 1.20.5+ from remapping Geyser
manifest {
attributes["paperweight-mappings-namespace"] = "mojang"
}
archiveBaseName.set("Geyser-Spigot") archiveBaseName.set("Geyser-Spigot")
dependencies { dependencies {
@ -48,6 +56,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
// We cannot shade Netty, or else native libraries will not load // We cannot shade Netty, or else native libraries will not load
// Needed because older Spigot builds do not provide the haproxy module // Needed because older Spigot builds do not provide the haproxy module
exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("io.netty:netty-transport-classes-epoll:.*")) exclude(dependency("io.netty:netty-transport-classes-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-epoll:.*")) exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-unix-common:.*")) exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
@ -67,3 +76,8 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
exclude(dependency("com.mojang:.*")) exclude(dependency("com.mojang:.*"))
} }
} }
modrinth {
uploadFile.set(tasks.getByPath("shadowJar"))
loaders.addAll("spigot", "paper")
}

View file

@ -34,8 +34,8 @@ import java.util.logging.Logger;
public final class GeyserPaperLogger extends GeyserSpigotLogger { public final class GeyserPaperLogger extends GeyserSpigotLogger {
private final ComponentLogger componentLogger; private final ComponentLogger componentLogger;
public GeyserPaperLogger(Plugin plugin, Logger logger, boolean debug) { public GeyserPaperLogger(Plugin plugin, Logger logger) {
super(logger, debug); super(logger);
componentLogger = plugin.getComponentLogger(); componentLogger = plugin.getComponentLogger();
} }

View file

@ -25,7 +25,7 @@
package org.geysermc.geyser.platform.spigot; package org.geysermc.geyser.platform.spigot;
import com.github.steveice10.mc.protocol.MinecraftProtocol; import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer; import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*; import io.netty.channel.*;
@ -119,8 +119,11 @@ public class GeyserSpigotInjector extends GeyserInjector {
protected void initChannel(@NonNull Channel ch) throws Exception { protected void initChannel(@NonNull Channel ch) throws Exception {
initChannel.invoke(childHandler, ch); initChannel.invoke(childHandler, ch);
int index = ch.pipeline().names().indexOf("encoder");
String baseName = index != -1 ? "encoder" : "outbound_config";
if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserSpigotCompressionDisabler.ENABLED) { if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserSpigotCompressionDisabler.ENABLED) {
ch.pipeline().addAfter("encoder", "geyser-compression-disabler", new GeyserSpigotCompressionDisabler()); ch.pipeline().addAfter(baseName, "geyser-compression-disabler", new GeyserSpigotCompressionDisabler());
} }
} }
}) })
@ -177,6 +180,7 @@ public class GeyserSpigotInjector extends GeyserInjector {
bootstrap.getGeyserConfig().getRemote().port(), this.serverSocketAddress, bootstrap.getGeyserConfig().getRemote().port(), this.serverSocketAddress,
InetAddress.getLoopbackAddress().getHostAddress(), protocol, protocol.createHelper()); InetAddress.getLoopbackAddress().getHostAddress(), protocol, protocol.createHelper());
session.connect(); session.connect();
session.disconnect("");
} }
@Override @Override

View file

@ -25,15 +25,15 @@
package org.geysermc.geyser.platform.spigot; package org.geysermc.geyser.platform.spigot;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.GeyserLogger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@AllArgsConstructor @RequiredArgsConstructor
public class GeyserSpigotLogger implements GeyserLogger { public class GeyserSpigotLogger implements GeyserLogger {
private final Logger logger; private final Logger logger;
@Getter @Setter @Getter @Setter

View file

@ -46,6 +46,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.Constants; import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.adapters.paper.PaperAdapters;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.extension.Extension;
@ -78,18 +79,14 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level;
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
/**
* Determines if the plugin has been ran once before, including before /geyser reload.
*/
private static boolean INITIALIZED = false;
private GeyserSpigotCommandManager geyserCommandManager; private GeyserSpigotCommandManager geyserCommandManager;
private GeyserSpigotConfiguration geyserConfig; private GeyserSpigotConfiguration geyserConfig;
private GeyserSpigotInjector geyserInjector; private GeyserSpigotInjector geyserInjector;
private GeyserSpigotLogger geyserLogger; private final GeyserSpigotLogger geyserLogger = GeyserPaperLogger.supported() ?
new GeyserPaperLogger(this, getLogger()) : new GeyserSpigotLogger(getLogger());
private IGeyserPingPassthrough geyserSpigotPingPassthrough; private IGeyserPingPassthrough geyserSpigotPingPassthrough;
private GeyserSpigotWorldManager geyserWorldManager; private GeyserSpigotWorldManager geyserWorldManager;
@ -102,6 +99,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
@Override @Override
public void onLoad() { public void onLoad() {
onGeyserInitialize();
}
@Override
public void onGeyserInitialize() {
GeyserLocale.init(this); GeyserLocale.init(this);
try { try {
@ -112,12 +114,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
// We depend on this as a fallback in certain scenarios // We depend on this as a fallback in certain scenarios
BlockData.class.getMethod("getAsString"); BlockData.class.getMethod("getAsString");
} catch (ClassNotFoundException | NoSuchMethodException e) { } catch (ClassNotFoundException | NoSuchMethodException e) {
getLogger().severe("*********************************************"); geyserLogger.error("*********************************************");
getLogger().severe(""); geyserLogger.error("");
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.header")); geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.header"));
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2")); geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2"));
getLogger().severe(""); geyserLogger.error("");
getLogger().severe("*********************************************"); geyserLogger.error("*********************************************");
Bukkit.getPluginManager().disablePlugin(this);
return; return;
} }
@ -125,12 +128,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
Class.forName("net.md_5.bungee.chat.ComponentSerializer"); Class.forName("net.md_5.bungee.chat.ComponentSerializer");
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
if (!PaperAdventure.canSendMessageUsingComponent()) { // Prepare for Paper eventually removing Bungee chat if (!PaperAdventure.canSendMessageUsingComponent()) { // Prepare for Paper eventually removing Bungee chat
getLogger().severe("*********************************************"); geyserLogger.error("*********************************************");
getLogger().severe(""); geyserLogger.error("");
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.header", getServer().getName())); geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.header", getServer().getName()));
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper")); geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper"));
getLogger().severe(""); geyserLogger.error("");
getLogger().severe("*********************************************"); geyserLogger.error("*********************************************");
Bukkit.getPluginManager().disablePlugin(this);
return; return;
} }
} }
@ -138,91 +142,76 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
try { try {
Class.forName("io.netty.util.internal.ObjectPool$ObjectCreator"); Class.forName("io.netty.util.internal.ObjectPool$ObjectCreator");
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
getLogger().severe("*********************************************"); geyserLogger.error("*********************************************");
getLogger().severe(""); geyserLogger.error("");
getLogger().severe("This version of Spigot is using an outdated version of netty. Please use Paper instead!"); geyserLogger.error("This version of Spigot is using an outdated version of netty. Please use Paper instead!");
getLogger().severe(""); geyserLogger.error("");
getLogger().severe("*********************************************"); geyserLogger.error("*********************************************");
return;
}
// This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
try {
if (!getDataFolder().exists()) {
//noinspection ResultOfMethodCallIgnored
getDataFolder().mkdir();
}
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml",
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class);
} catch (IOException ex) {
getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
Bukkit.getPluginManager().disablePlugin(this); Bukkit.getPluginManager().disablePlugin(this);
return; return;
} }
this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode()) if (!loadConfig()) {
: new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); return;
}
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
// Turn "(MC: 1.16.4)" into 1.16.4.
this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
this.geyser = GeyserImpl.load(PlatformType.SPIGOT, this); this.geyser = GeyserImpl.load(PlatformType.SPIGOT, this);
} }
@Override @Override
public void onEnable() { public void onEnable() {
if (this.geyserConfig == null) {
// We failed to initialize correctly
Bukkit.getPluginManager().disablePlugin(this);
return;
}
this.geyserCommandManager = new GeyserSpigotCommandManager(geyser); this.geyserCommandManager = new GeyserSpigotCommandManager(geyser);
this.geyserCommandManager.init(); this.geyserCommandManager.init();
if (!INITIALIZED) { // Because Bukkit locks its command map upon startup, we need to
// Needs to be an anonymous inner class otherwise Bukkit complains about missing classes // add our plugin commands in onEnable, but populating the executor
Bukkit.getPluginManager().registerEvents(new Listener() { // can happen at any time (later in #onGeyserEnable())
CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap();
for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) {
// Thanks again, Bukkit
try {
Constructor<PluginCommand> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
constructor.setAccessible(true);
@EventHandler PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this);
public void onServerLoaded(ServerLoadEvent event) { pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!");
// Wait until all plugins have loaded so Geyser can start
postStartup();
}
}, this);
// Because Bukkit locks its command map upon startup, we need to commandMap.register(extension.description().id(), "geyserext", pluginCommand);
// add our plugin commands in onEnable, but populating the executor } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
// can happen at any time this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap();
for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) {
// Thanks again, Bukkit
try {
Constructor<PluginCommand> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
constructor.setAccessible(true);
PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this);
pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!");
commandMap.register(extension.description().id(), "geyserext", pluginCommand);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
}
} }
} }
if (INITIALIZED) { // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
// Reload; continue with post startup Bukkit.getPluginManager().registerEvents(new Listener() {
postStartup();
} @EventHandler
public void onServerLoaded(ServerLoadEvent event) {
if (event.getType() == ServerLoadEvent.LoadType.RELOAD) {
geyser.setShuttingDown(false);
}
onGeyserEnable();
}
}, this);
} }
private void postStartup() { public void onGeyserEnable() {
GeyserImpl.start(); // Configs are loaded once early - so we can create the logger, then load extensions and finally register
// extension commands in #onEnable. To ensure reloading geyser also reloads the geyser config, this exists
if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) {
return;
}
this.geyserLogger.setDebug(this.geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
}
// Turn "(MC: 1.16.4)" into 1.16.4. GeyserImpl.start();
this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
if (geyserConfig.isLegacyPingPassthrough()) { if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
@ -238,20 +227,16 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
} }
geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass())); geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass()));
boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; // Don't need to re-create the world manager/re-register commands/reinject when reloading
if (isViaVersion) { if (GeyserImpl.getInstance().isReloading()) {
try { return;
// Ensure that we have the latest 4.0.0 changes and not an older ViaVersion version
Class.forName("com.viaversion.viaversion.api.ViaManager");
} catch (ClassNotFoundException e) {
GeyserSpigotVersionChecker.sendOutdatedViaVersionMessage(geyserLogger);
isViaVersion = false;
if (this.geyserConfig.isDebugMode()) {
e.printStackTrace();
}
}
} }
boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null;
// Check to ensure the current setup can support the protocol version Geyser uses
GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion);
// We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib // We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib
// To do their job injecting, then connect into *that* // To do their job injecting, then connect into *that*
this.geyserInjector = new GeyserSpigotInjector(isViaVersion); this.geyserInjector = new GeyserSpigotInjector(isViaVersion);
@ -259,17 +244,29 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
if (Boolean.parseBoolean(System.getProperty("Geyser.UseDirectAdapters", "true"))) { if (Boolean.parseBoolean(System.getProperty("Geyser.UseDirectAdapters", "true"))) {
try { try {
String name = Bukkit.getServer().getClass().getPackage().getName(); boolean isPaper = false;
String nmsVersion = name.substring(name.lastIndexOf('.') + 1); try {
SpigotAdapters.registerWorldAdapter(nmsVersion); String name = Bukkit.getServer().getClass().getPackage().getName();
String nmsVersion = name.substring(name.lastIndexOf('.') + 1);
SpigotAdapters.registerWorldAdapter(nmsVersion);
geyserLogger.debug("Using spigot NMS adapter for nms version: " + nmsVersion);
} catch (Exception e) { // Likely running on Paper 1.20.5+
geyserLogger.debug("Unable to find spigot world manager: " + e.getMessage());
//noinspection deprecation
int protocolVersion = Bukkit.getUnsafe().getProtocolVersion();
PaperAdapters.registerClosestWorldAdapter(protocolVersion);
isPaper = true;
geyserLogger.debug("Using paper world adapter for protocol version: " + protocolVersion);
}
if (isViaVersion && isViaVersionNeeded()) { if (isViaVersion && isViaVersionNeeded()) {
this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this); this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this, isPaper);
} else { } else {
// No ViaVersion // No ViaVersion
this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this); this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this, isPaper);
} }
geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion); geyserLogger.debug("Using world manager of type: " + this.geyserWorldManager.getClass().getSimpleName());
} catch (Exception e) { } catch (Throwable e) {
if (geyserConfig.isDebugMode()) { if (geyserConfig.isDebugMode()) {
geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)"); geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)");
e.printStackTrace(); e.printStackTrace();
@ -278,6 +275,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
} else { } else {
geyserLogger.debug("Not using NMS adapter as it is disabled via system property."); geyserLogger.debug("Not using NMS adapter as it is disabled via system property.");
} }
if (this.geyserWorldManager == null) { if (this.geyserWorldManager == null) {
// No NMS adapter // No NMS adapter
this.geyserWorldManager = new GeyserSpigotWorldManager(this); this.geyserWorldManager = new GeyserSpigotWorldManager(this);
@ -302,72 +300,72 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands)); command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands));
} }
if (!INITIALIZED) { // Register permissions so they appear in, for example, LuckPerms' UI
// Register permissions so they appear in, for example, LuckPerms' UI // Re-registering permissions throws an error
// Re-registering permissions throws an error for (Map.Entry<String, Command> entry : geyserCommandManager.commands().entrySet()) {
for (Map.Entry<String, Command> entry : geyserCommandManager.commands().entrySet()) { Command command = entry.getValue();
if (command.aliases().contains(entry.getKey())) {
// Don't register aliases
continue;
}
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
GeyserLocale.getLocaleStringLog(command.description()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
}
// Register permissions for extension commands
for (Map.Entry<Extension, Map<String, Command>> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) {
for (Map.Entry<String, Command> entry : commandEntry.getValue().entrySet()) {
Command command = entry.getValue(); Command command = entry.getValue();
if (command.aliases().contains(entry.getKey())) { if (command.aliases().contains(entry.getKey())) {
// Don't register aliases // Don't register aliases
continue; continue;
} }
if (command.permission().isBlank()) {
continue;
}
// Avoid registering the same permission twice, e.g. for the extension help commands
if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
continue;
}
Bukkit.getPluginManager().addPermission(new Permission(command.permission(), Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
GeyserLocale.getLocaleStringLog(command.description()), GeyserLocale.getLocaleStringLog(command.description()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE)); command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
} }
// Register permissions for extension commands
for (Map.Entry<Extension, Map<String, Command>> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) {
for (Map.Entry<String, Command> entry : commandEntry.getValue().entrySet()) {
Command command = entry.getValue();
if (command.aliases().contains(entry.getKey())) {
// Don't register aliases
continue;
}
if (command.permission().isBlank()) {
continue;
}
// Avoid registering the same permission twice, e.g. for the extension help commands
if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
continue;
}
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
GeyserLocale.getLocaleStringLog(command.description()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
}
}
Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION,
"Whether update notifications can be seen", PermissionDefault.OP));
// Events cannot be unregistered - re-registering results in duplicate firings
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this);
} }
Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION,
"Whether update notifications can be seen", PermissionDefault.OP));
// Events cannot be unregistered - re-registering results in duplicate firings
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this);
boolean brigadierSupported = CommodoreProvider.isSupported(); boolean brigadierSupported = CommodoreProvider.isSupported();
geyserLogger.debug("Brigadier supported? " + brigadierSupported); geyserLogger.debug("Brigadier supported? " + brigadierSupported);
if (brigadierSupported) { if (brigadierSupported) {
GeyserBrigadierSupport.loadBrigadier(this, geyserCommand); GeyserBrigadierSupport.loadBrigadier(this, geyserCommand);
} }
// Check to ensure the current setup can support the protocol version Geyser uses
GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion);
INITIALIZED = true;
} }
@Override @Override
public void onDisable() { public void onGeyserDisable() {
if (geyser != null) {
geyser.disable();
}
}
@Override
public void onGeyserShutdown() {
if (geyser != null) { if (geyser != null) {
geyser.shutdown(); geyser.shutdown();
} }
@ -376,6 +374,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
} }
} }
@Override
public void onDisable() {
this.onGeyserShutdown();
}
@Override @Override
public GeyserSpigotConfiguration getGeyserConfig() { public GeyserSpigotConfiguration getGeyserConfig() {
return geyserConfig; return geyserConfig;
@ -442,7 +445,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
return false; return false;
} }
for (int i = protocolList.size() - 1; i >= 0; i--) { for (int i = protocolList.size() - 1; i >= 0; i--) {
MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); MappingData mappingData = protocolList.get(i).protocol().getMappingData();
if (mappingData != null) { if (mappingData != null) {
return true; return true;
} }
@ -470,4 +473,25 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
} }
return false; return false;
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean loadConfig() {
// This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
try {
if (!getDataFolder().exists()) {
//noinspection ResultOfMethodCallIgnored
getDataFolder().mkdir();
}
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml",
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class);
} catch (IOException ex) {
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
Bukkit.getPluginManager().disablePlugin(this);
return false;
}
return true;
}
} }

View file

@ -25,7 +25,7 @@
package org.geysermc.geyser.platform.spigot; package org.geysermc.geyser.platform.spigot;
import com.github.steveice10.mc.protocol.data.DefaultComponentSerializer; import org.geysermc.mcprotocollib.protocol.data.DefaultComponentSerializer;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;

View file

@ -25,10 +25,8 @@
package org.geysermc.geyser.platform.spigot.world; package org.geysermc.geyser.platform.spigot.world;
import com.github.steveice10.mc.protocol.data.game.level.block.value.PistonValueType; import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
import org.cloudburstmc.math.vector.Vector3i; import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
@ -40,13 +38,17 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPistonEvent; import org.bukkit.event.block.BlockPistonEvent;
import org.bukkit.event.block.BlockPistonExtendEvent; import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPistonRetractEvent; import org.bukkit.event.block.BlockPistonRetractEvent;
import org.cloudburstmc.math.vector.Vector3i;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.physics.Direction; import org.geysermc.geyser.level.physics.Direction;
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager; import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.PistonCache; import org.geysermc.geyser.session.cache.PistonCache;
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity; import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -85,7 +87,7 @@ public class GeyserPistonListener implements Listener {
PistonValueType type = isExtend ? PistonValueType.PUSHING : PistonValueType.PULLING; PistonValueType type = isExtend ? PistonValueType.PUSHING : PistonValueType.PULLING;
boolean sticky = event.isSticky(); boolean sticky = event.isSticky();
Object2IntMap<Vector3i> attachedBlocks = new Object2IntArrayMap<>(); Object2ObjectMap<Vector3i, BlockState> attachedBlocks = new Object2ObjectArrayMap<>();
boolean blocksFilled = false; boolean blocksFilled = false;
for (Map.Entry<UUID, GeyserSession> entry : geyser.getSessionManager().getSessions().entrySet()) { for (Map.Entry<UUID, GeyserSession> entry : geyser.getSessionManager().getSessions().entrySet()) {
@ -108,10 +110,10 @@ public class GeyserPistonListener implements Listener {
List<Block> blocks = isExtend ? ((BlockPistonExtendEvent) event).getBlocks() : ((BlockPistonRetractEvent) event).getBlocks(); List<Block> blocks = isExtend ? ((BlockPistonExtendEvent) event).getBlocks() : ((BlockPistonRetractEvent) event).getBlocks();
for (Block block : blocks) { for (Block block : blocks) {
Location attachedLocation = block.getLocation(); Location attachedLocation = block.getLocation();
int blockId = worldManager.getBlockNetworkId(block); BlockState state = BlockState.of(worldManager.getBlockNetworkId(block));
// Ignore blocks that will be destroyed // Ignore blocks that will be destroyed
if (BlockStateValues.canPistonMoveBlock(blockId, isExtend)) { if (BlockStateValues.canPistonMoveBlock(state, isExtend)) {
attachedBlocks.put(getVector(attachedLocation), blockId); attachedBlocks.put(getVector(attachedLocation), state);
} }
} }
blocksFilled = true; blocksFilled = true;
@ -119,7 +121,7 @@ public class GeyserPistonListener implements Listener {
int pistonBlockId = worldManager.getBlockNetworkId(event.getBlock()); int pistonBlockId = worldManager.getBlockNetworkId(event.getBlock());
// event.getDirection() is unreliable // event.getDirection() is unreliable
Direction orientation = BlockStateValues.getPistonOrientation(pistonBlockId); Direction orientation = BlockState.of(pistonBlockId).getValue(Properties.FACING);
session.executeInEventLoop(() -> { session.executeInEventLoop(() -> {
PistonCache pistonCache = session.getPistonCache(); PistonCache pistonCache = session.getPistonCache();

View file

@ -33,7 +33,7 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager; import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
@ -59,11 +59,11 @@ public class GeyserSpigotBlockPlaceListener implements Listener {
event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ()))); event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())));
} else { } else {
String javaBlockId = event.getBlockPlaced().getBlockData().getAsString(); String javaBlockId = event.getBlockPlaced().getBlockData().getAsString();
placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().getOrDefault(javaBlockId, BlockStateValues.JAVA_AIR_ID))); placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().getOrDefault(javaBlockId, Block.JAVA_AIR_ID)));
} }
placeBlockSoundPacket.setIdentifier(":"); placeBlockSoundPacket.setIdentifier(":");
session.sendUpstreamPacket(placeBlockSoundPacket); session.sendUpstreamPacket(placeBlockSoundPacket);
session.setLastBlockPlacePosition(null); session.setLastBlockPlacePosition(null);
session.setLastBlockPlacedId(null); session.setLastBlockPlaced(null);
} }
} }

View file

@ -46,8 +46,8 @@ public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorl
private final Int2IntMap oldToNewBlockId; private final Int2IntMap oldToNewBlockId;
public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin) { public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin, boolean isPaper) {
super(plugin); super(plugin, isPaper);
IntList allBlockStates = adapter.getAllBlockStates(); IntList allBlockStates = adapter.getAllBlockStates();
oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size()); oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size());
ProtocolVersion serverVersion = plugin.getServerProtocolVersion(); ProtocolVersion serverVersion = plugin.getServerProtocolVersion();
@ -58,7 +58,7 @@ public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorl
int newBlockId = oldBlockId; int newBlockId = oldBlockId;
// protocolList should *not* be null; we checked for that before initializing this class // protocolList should *not* be null; we checked for that before initializing this class
for (int i = protocolList.size() - 1; i >= 0; i--) { for (int i = protocolList.size() - 1; i >= 0; i--) {
MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); MappingData mappingData = protocolList.get(i).protocol().getMappingData();
if (mappingData != null) { if (mappingData != null) {
newBlockId = mappingData.getNewBlockStateId(newBlockId); newBlockId = mappingData.getNewBlockStateId(newBlockId);
} }

View file

@ -26,27 +26,34 @@
package org.geysermc.geyser.platform.spigot.world.manager; package org.geysermc.geyser.platform.spigot.world.manager;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.adapters.WorldAdapter;
import org.geysermc.geyser.adapters.paper.PaperAdapters;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager { public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
protected final SpigotWorldAdapter adapter; protected final WorldAdapter<World> adapter;
public GeyserSpigotNativeWorldManager(Plugin plugin) { public GeyserSpigotNativeWorldManager(Plugin plugin, boolean isPaper) {
super(plugin); super(plugin);
adapter = SpigotAdapters.getWorldAdapter(); if (isPaper) {
adapter = PaperAdapters.getWorldAdapter();
} else {
adapter = SpigotAdapters.getWorldAdapter();
}
} }
@Override @Override
public int getBlockAt(GeyserSession session, int x, int y, int z) { public int getBlockAt(GeyserSession session, int x, int y, int z) {
Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername());
if (player == null) { if (player == null) {
return BlockStateValues.JAVA_AIR_ID; return Block.JAVA_AIR_ID;
} }
return adapter.getBlockAt(player.getWorld(), x, y, z); return adapter.getBlockAt(player.getWorld(), x, y, z);
} }

View file

@ -25,56 +25,52 @@
package org.geysermc.geyser.platform.spigot.world.manager; package org.geysermc.geyser.platform.spigot.world.manager;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.DecoratedPot;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.math.vector.Vector3i;
import org.geysermc.erosion.bukkit.BukkitLecterns;
import org.geysermc.erosion.bukkit.BukkitUtils; import org.geysermc.erosion.bukkit.BukkitUtils;
import org.geysermc.erosion.bukkit.PickBlockUtils; import org.geysermc.erosion.bukkit.PickBlockUtils;
import org.geysermc.erosion.bukkit.SchedulerUtils; import org.geysermc.erosion.bukkit.SchedulerUtils;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.level.GameRule; import org.geysermc.geyser.level.GameRule;
import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.BlockEntityUtils; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/** /**
* The base world manager to use when there is no supported NMS revision * The base world manager to use when there is no supported NMS revision
*/ */
public class GeyserSpigotWorldManager extends WorldManager { public class GeyserSpigotWorldManager extends WorldManager {
private final Plugin plugin; private final Plugin plugin;
private final BukkitLecterns lecterns;
public GeyserSpigotWorldManager(Plugin plugin) { public GeyserSpigotWorldManager(Plugin plugin) {
this.plugin = plugin; this.plugin = plugin;
this.lecterns = new BukkitLecterns(plugin);
} }
@Override @Override
public int getBlockAt(GeyserSession session, int x, int y, int z) { public int getBlockAt(GeyserSession session, int x, int y, int z) {
Player bukkitPlayer; Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
return BlockStateValues.JAVA_AIR_ID; return org.geysermc.geyser.level.block.type.Block.JAVA_AIR_ID;
} }
World world = bukkitPlayer.getWorld(); World world = bukkitPlayer.getWorld();
if (!world.isChunkLoaded(x >> 4, z >> 4)) { if (!world.isChunkLoaded(x >> 4, z >> 4)) {
// If the chunk isn't loaded, how could we even be here? // If the chunk isn't loaded, how could we even be here?
return BlockStateValues.JAVA_AIR_ID; return org.geysermc.geyser.level.block.type.Block.JAVA_AIR_ID;
} }
return getBlockNetworkId(world.getBlockAt(x, y, z)); return getBlockNetworkId(world.getBlockAt(x, y, z));
@ -85,9 +81,9 @@ public class GeyserSpigotWorldManager extends WorldManager {
// Terrible behavior, but this is basically what's always been happening behind the scenes anyway. // Terrible behavior, but this is basically what's always been happening behind the scenes anyway.
CompletableFuture<String> blockData = new CompletableFuture<>(); CompletableFuture<String> blockData = new CompletableFuture<>();
Bukkit.getRegionScheduler().execute(this.plugin, block.getLocation(), () -> blockData.complete(block.getBlockData().getAsString())); Bukkit.getRegionScheduler().execute(this.plugin, block.getLocation(), () -> blockData.complete(block.getBlockData().getAsString()));
return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(blockData.join(), BlockStateValues.JAVA_AIR_ID); return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(blockData.join(), org.geysermc.geyser.level.block.type.Block.JAVA_AIR_ID);
} }
return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(block.getBlockData().getAsString(), BlockStateValues.JAVA_AIR_ID); return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(block.getBlockData().getAsString(), org.geysermc.geyser.level.block.type.Block.JAVA_AIR_ID); // TODO could just make this a BlockState lookup?
} }
@Override @Override
@ -95,69 +91,6 @@ public class GeyserSpigotWorldManager extends WorldManager {
return true; return true;
} }
@Override
public void sendLecternData(GeyserSession session, int x, int y, int z) {
Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
return;
}
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
// Run as a task to prevent async issues
SchedulerUtils.runTask(this.plugin, () -> sendLecternData(session, block, false), block);
}
public void sendLecternData(GeyserSession session, int x, int z, List<BlockEntityInfo> blockEntityInfos) {
Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
return;
}
if (SchedulerUtils.FOLIA) {
Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
if (chunk == null) {
return;
}
Bukkit.getRegionScheduler().execute(this.plugin, bukkitPlayer.getWorld(), x, z, () ->
sendLecternData(session, chunk, blockEntityInfos));
} else {
Bukkit.getScheduler().runTask(this.plugin, () -> {
Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
if (chunk == null) {
return;
}
sendLecternData(session, chunk, blockEntityInfos);
});
}
}
private @Nullable Chunk getChunk(World world, int x, int z) {
if (!world.isChunkLoaded(x, z)) {
return null;
}
return world.getChunkAt(x, z);
}
private void sendLecternData(GeyserSession session, Chunk chunk, List<BlockEntityInfo> blockEntityInfos) {
//noinspection ForLoopReplaceableByForEach - avoid constructing Iterator
for (int i = 0; i < blockEntityInfos.size(); i++) {
BlockEntityInfo info = blockEntityInfos.get(i);
Block block = chunk.getBlock(info.getX(), info.getY(), info.getZ());
sendLecternData(session, block, true);
}
}
private void sendLecternData(GeyserSession session, Block block, boolean isChunkLoad) {
NbtMap blockEntityTag = this.lecterns.getLecternData(block, isChunkLoad);
if (blockEntityTag != null) {
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, BukkitUtils.getVector(block.getLocation()));
}
}
@Override
public boolean shouldExpectLecternHandled(GeyserSession session) {
return true;
}
public boolean getGameRuleBool(GeyserSession session, GameRule gameRule) { public boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
org.bukkit.GameRule<?> bukkitGameRule = org.bukkit.GameRule.getByName(gameRule.getJavaID()); org.bukkit.GameRule<?> bukkitGameRule = org.bukkit.GameRule.getByName(gameRule.getJavaID());
if (bukkitGameRule == null) { if (bukkitGameRule == null) {
@ -205,18 +138,32 @@ public class GeyserSpigotWorldManager extends WorldManager {
} }
@Override @Override
public @NonNull CompletableFuture<@Nullable CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) { public @NonNull CompletableFuture<@Nullable DataComponents> getPickItemComponents(GeyserSession session, int x, int y, int z, boolean addNbtData) {
CompletableFuture<@Nullable CompoundTag> future = new CompletableFuture<>();
Player bukkitPlayer; Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) { if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {
future.complete(null); return CompletableFuture.completedFuture(null);
return future;
} }
CompletableFuture<Int2ObjectMap<byte[]>> future = new CompletableFuture<>();
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z); Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
// Paper 1.19.3 complains about async access otherwise. // Paper 1.19.3 complains about async access otherwise.
// java.lang.IllegalStateException: Tile is null, asynchronous access? // java.lang.IllegalStateException: Tile is null, asynchronous access?
SchedulerUtils.runTask(this.plugin, () -> future.complete(PickBlockUtils.pickBlock(block)), block); SchedulerUtils.runTask(this.plugin, () -> future.complete(PickBlockUtils.pickBlock(block)), block);
return future; return future.thenApply(RAW_TRANSFORMER);
}
public void getDecoratedPotData(GeyserSession session, Vector3i pos, Consumer<List<String>> apply) {
Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {
return;
}
Block block = bukkitPlayer.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
SchedulerUtils.runTask(this.plugin, () -> {
var state = BukkitUtils.getBlockState(block);
if (!(state instanceof DecoratedPot pot)) {
return;
}
apply.accept(pot.getShards().stream().map(material -> material.getKey().toString()).toList());
}, block);
} }
/** /**

View file

@ -39,9 +39,9 @@ import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
@ -59,20 +59,24 @@ import java.lang.reflect.Method;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.*; import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class GeyserStandaloneBootstrap implements GeyserBootstrap { public class GeyserStandaloneBootstrap implements GeyserBootstrap {
private GeyserCommandManager geyserCommandManager; private GeyserCommandManager geyserCommandManager;
private GeyserStandaloneConfiguration geyserConfig; private GeyserStandaloneConfiguration geyserConfig;
private GeyserStandaloneLogger geyserLogger; private final GeyserStandaloneLogger geyserLogger = new GeyserStandaloneLogger();
private IGeyserPingPassthrough geyserPingPassthrough; private IGeyserPingPassthrough geyserPingPassthrough;
private GeyserStandaloneGUI gui; private GeyserStandaloneGUI gui;
@Getter @Getter
private boolean useGui = System.console() == null && !isHeadless(); private boolean useGui = System.console() == null && !isHeadless();
private Logger log4jLogger;
private String configFilename = "config.yml"; private String configFilename = "config.yml";
private GeyserImpl geyser; private GeyserImpl geyser;
@ -161,28 +165,22 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
} }
} }
} }
bootstrap.onEnable(useGuiOpts, configFilenameOpt); bootstrap.useGui = useGuiOpts;
} bootstrap.configFilename = configFilenameOpt;
bootstrap.onGeyserInitialize();
public void onEnable(boolean useGui, String configFilename) {
this.configFilename = configFilename;
this.useGui = useGui;
this.onEnable();
} }
@Override @Override
public void onEnable() { public void onGeyserInitialize() {
Logger logger = (Logger) LogManager.getRootLogger(); log4jLogger = (Logger) LogManager.getRootLogger();
for (Appender appender : logger.getAppenders().values()) { for (Appender appender : log4jLogger.getAppenders().values()) {
// Remove the appender that is not in use // Remove the appender that is not in use
// Prevents multiple appenders/double logging and removes harmless errors // Prevents multiple appenders/double logging and removes harmless errors
if ((useGui && appender instanceof TerminalConsoleAppender) || (!useGui && appender instanceof ConsoleAppender)) { if ((useGui && appender instanceof TerminalConsoleAppender) || (!useGui && appender instanceof ConsoleAppender)) {
logger.removeAppender(appender); log4jLogger.removeAppender(appender);
} }
} }
this.geyserLogger = new GeyserStandaloneLogger();
if (useGui && gui == null) { if (useGui && gui == null) {
gui = new GeyserStandaloneGUI(geyserLogger); gui = new GeyserStandaloneGUI(geyserLogger);
gui.redirectSystemStreams(); gui.redirectSystemStreams();
@ -191,6 +189,11 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
LoopbackUtil.checkAndApplyLoopback(geyserLogger); LoopbackUtil.checkAndApplyLoopback(geyserLogger);
this.onGeyserEnable();
}
@Override
public void onGeyserEnable() {
try { try {
File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml",
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
@ -215,14 +218,15 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
// Allow libraries like Protocol to have their debug information passthrough // Allow libraries like Protocol to have their debug information passthrough
logger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO); log4jLogger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO);
geyser = GeyserImpl.load(PlatformType.STANDALONE, this); geyser = GeyserImpl.load(PlatformType.STANDALONE, this);
GeyserImpl.start();
geyserCommandManager = new GeyserCommandManager(geyser); geyserCommandManager = new GeyserCommandManager(geyser);
geyserCommandManager.init(); geyserCommandManager.init();
GeyserImpl.start();
if (gui != null) { if (gui != null) {
gui.enableCommands(geyser.getScheduledThread(), geyserCommandManager); gui.enableCommands(geyser.getScheduledThread(), geyserCommandManager);
} }
@ -250,7 +254,14 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
} }
@Override @Override
public void onDisable() { public void onGeyserDisable() {
// We can re-register commands on standalone, so why not
GeyserImpl.getInstance().commandManager().getCommands().clear();
geyser.disable();
}
@Override
public void onGeyserShutdown() {
geyser.shutdown(); geyser.shutdown();
System.exit(0); System.exit(0);
} }

View file

@ -49,7 +49,7 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
@Override @Override
protected void shutdown() { protected void shutdown() {
GeyserImpl.getInstance().getBootstrap().onDisable(); GeyserImpl.getInstance().getBootstrap().onGeyserShutdown();
} }
@Override @Override

View file

@ -100,7 +100,7 @@ public class GeyserStandaloneGUI {
Container cp = frame.getContentPane(); Container cp = frame.getContentPane();
// Fetch and set the icon for the frame // Fetch and set the icon for the frame
URL image = getClass().getClassLoader().getResource("icon.png"); URL image = getClass().getClassLoader().getResource("assets/geyser/icon.png");
if (image != null) { if (image != null) {
ImageIcon icon = new ImageIcon(image); ImageIcon icon = new ImageIcon(image);
frame.setIconImage(icon.getImage()); frame.setIconImage(icon.getImage());

View file

@ -1,15 +1,19 @@
dependencies { dependencies {
annotationProcessor(libs.velocity.api) annotationProcessor(libs.velocity.api)
api(projects.core) api(projects.core)
compileOnlyApi(libs.velocity.api)
} }
platformRelocate("com.fasterxml.jackson") platformRelocate("com.fasterxml.jackson")
platformRelocate("it.unimi.dsi.fastutil") platformRelocate("it.unimi.dsi.fastutil")
platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl") platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl")
platformRelocate("org.yaml")
exclude("com.google.*:*") exclude("com.google.*:*")
// Needed because Velocity provides every dependency except netty-resolver-dns // Needed because Velocity provides every dependency except netty-resolver-dns
exclude("io.netty.incubator:.*")
exclude("io.netty:netty-transport-native-epoll:*") exclude("io.netty:netty-transport-native-epoll:*")
exclude("io.netty:netty-transport-native-unix-common:*") exclude("io.netty:netty-transport-native-unix-common:*")
exclude("io.netty:netty-transport-native-kqueue:*") exclude("io.netty:netty-transport-native-kqueue:*")
@ -54,6 +58,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
exclude(dependency("io.netty:netty-transport:.*")) exclude(dependency("io.netty:netty-transport:.*"))
exclude(dependency("io.netty:netty-codec:.*")) exclude(dependency("io.netty:netty-codec:.*"))
exclude(dependency("io.netty:netty-codec-haproxy:.*")) exclude(dependency("io.netty:netty-codec-haproxy:.*"))
exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("org.slf4j:.*")) exclude(dependency("org.slf4j:.*"))
exclude(dependency("org.ow2.asm:.*")) exclude(dependency("org.ow2.asm:.*"))
// Exclude all Kyori dependencies except the legacy NBT serializer // Exclude all Kyori dependencies except the legacy NBT serializer
@ -65,3 +70,8 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
exclude(dependency("net.kyori:adventure-nbt:.*")) exclude(dependency("net.kyori:adventure-nbt:.*"))
} }
} }
modrinth {
uploadFile.set(tasks.getByPath("shadowJar"))
loaders.addAll("velocity")
}

View file

@ -47,8 +47,14 @@ public class GeyserVelocityCompressionDisabler extends ChannelDuplexHandler {
Method setCompressionMethod = null; Method setCompressionMethod = null;
try { try {
compressionPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.SetCompression"); try {
loginSuccessPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess"); compressionPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.SetCompressionPacket");
loginSuccessPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket");
} catch (Exception ignored) {
// Velocity renamed packet classes in https://github.com/PaperMC/Velocity/commit/2ac8751337befd04f4663575f5d752c748384110
compressionPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.SetCompression");
loginSuccessPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess");
}
compressionEnabledEvent = Class.forName("com.velocitypowered.proxy.protocol.VelocityConnectionEvent") compressionEnabledEvent = Class.forName("com.velocitypowered.proxy.protocol.VelocityConnectionEvent")
.getDeclaredField("COMPRESSION_ENABLED").get(null); .getDeclaredField("COMPRESSION_ENABLED").get(null);
setCompressionMethod = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection") setCompressionMethod = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection")

View file

@ -25,13 +25,13 @@
package org.geysermc.geyser.platform.velocity; package org.geysermc.geyser.platform.velocity;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.GeyserLogger;
import org.slf4j.Logger; import org.slf4j.Logger;
@AllArgsConstructor @RequiredArgsConstructor
public class GeyserVelocityLogger implements GeyserLogger { public class GeyserVelocityLogger implements GeyserLogger {
private final Logger logger; private final Logger logger;
@Getter @Setter @Getter @Setter

View file

@ -26,6 +26,7 @@
package org.geysermc.geyser.platform.velocity; package org.geysermc.geyser.platform.velocity;
import com.velocitypowered.api.event.proxy.ProxyPingEvent; import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.network.ProtocolState;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
@ -88,6 +89,11 @@ public class GeyserVelocityPingPassthrough implements IGeyserPingPassthrough {
public ProtocolVersion getProtocolVersion() { public ProtocolVersion getProtocolVersion() {
return ProtocolVersion.MAXIMUM_VERSION; return ProtocolVersion.MAXIMUM_VERSION;
} }
@Override
public ProtocolState getProtocolState() {
return ProtocolState.STATUS;
}
} }
} }

Some files were not shown because too many files have changed in this diff Show more