Merge remote-tracking branch 'upstream/master' into extension-api-check

# Conflicts:
#	api/src/main/java/org/geysermc/geyser/api/GeyserApi.java
#	core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java
#	gradle/libs.versions.toml
This commit is contained in:
onebeastchris 2024-07-10 21:01:49 +02:00
commit 507819d66f
881 changed files with 48630 additions and 28598 deletions

14
.editorconfig Normal file
View file

@ -0,0 +1,14 @@
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
insert_final_newline = true
tab_width = 4
max_line_length = off
[*.java]
ij_java_class_count_to_use_import_on_demand = 9999
ij_java_doc_align_exception_comments = false
ij_java_doc_align_param_comments = false

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,128 +3,118 @@ 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'
- 'LICENSE' - 'LICENSE'
- 'Jenkinsfile ' - 'Jenkinsfile '
- 'README.md' - 'README.md'
- 'licenseheader.txt'
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository and submodules - name: Get Release Info
uses: actions/checkout@v3 id: release-info
uses: GeyserMC/actions/previous-release@master
with: with:
submodules: recursive data: ${{ vars.RELEASEACTION_PREVRELEASE }}
- name: Validate Gradle Wrapper - name: Setup Gradle
uses: gradle/wrapper-validation-action@v1 uses: GeyserMC/actions/setup-gradle-composite@master
with:
setup-java_java-version: 21
- uses: actions/setup-java@v3 - name: Build Geyser
with: run: ./gradlew build
java-version: 17 env:
distribution: temurin BUILD_NUMBER: ${{ steps.release-info.outputs.curentRelease }}
- name: Build - name: Archive Artifacts
uses: gradle/gradle-build-action@v2 uses: GeyserMC/actions/upload-multi-artifact@master
with:
arguments: build
gradle-home-cache-cleanup: true
- name: Archive artifacts (Geyser Fabric)
uses: actions/upload-artifact@v3
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@v3 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@v3
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@v3
if: success()
with:
name: Geyser BungeeCord
path: bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Sponge)
uses: actions/upload-artifact@v3
if: success()
with:
name: Geyser Sponge
path: bootstrap/sponge/build/libs/Geyser-Sponge.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Velocity)
uses: actions/upload-artifact@v3
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@v2 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@v2
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' }}
uses: Tim203/actions-git-discord-webhook@main uses: GeyserMC/actions/notify-discord@master
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,96 +0,0 @@
name: Build Pull Request
on:
pull_request:
paths-ignore:
- '.github/ISSUE_TEMPLATE/*.yml'
- '.idea/copyright/*.xml'
- '.gitignore'
- 'CONTRIBUTING.md'
- 'LICENSE'
- 'Jenkinsfile '
- 'README.md'
- 'licenseheader.txt'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: 17
distribution: temurin
- name: Check if the author has forked the API repo
uses: Kas-tle/find-forks-action@v1.0.1
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
uses: actions/checkout@v3
with:
submodules: recursive
path: geyser
- name: Build Geyser
uses: gradle/gradle-build-action@v2
with:
arguments: build
build-root-directory: geyser
- name: Archive artifacts (Geyser Fabric)
uses: actions/upload-artifact@v3
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@v3
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@v3
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@v3
if: success()
with:
name: Geyser BungeeCord
path: geyser/bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Sponge)
uses: actions/upload-artifact@v3
if: success()
with:
name: Geyser Sponge
path: geyser/bootstrap/sponge/build/libs/Geyser-Sponge.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Velocity)
uses: actions/upload-artifact@v3
if: success()
with:
name: Geyser Velocity
path: geyser/bootstrap/velocity/build/libs/Geyser-Velocity.jar
if-no-files-found: error

1
.gitignore vendored
View file

@ -251,3 +251,4 @@ locales/
/saved-refresh-tokens.json /saved-refresh-tokens.json
/custom_mappings/ /custom_mappings/
/languages/ /languages/
/custom-skulls.yml

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

@ -33,18 +33,14 @@ public class LongClassName {
} }
switch (value) { switch (value) {
case 0: case 0 -> stuff();
stuff(); case 1 -> differentStuff();
break;
case 1:
differentStuff();
break;
} }
} }
} }
``` ```
Make sure to comment your code where possible. Make sure to comment your code where possible. To mark nullable methods, use `@Nullable` (and subsequently, `@NonNull`) from the `org.checkerframework.checker.nullness.qual` package.
The nature of our software requires a lot of arrays and maps to be stored - where possible, use Fastutil's specialized maps. For example, if you're storing block state translations, use an `Int2IntMap`. The nature of our software requires a lot of arrays and maps to be stored - where possible, use Fastutil's specialized maps. For example, if you're storing block state translations, use an `Int2IntMap`.

28
Jenkinsfile vendored
View file

@ -1,28 +0,0 @@
pipeline {
agent any
tools {
gradle 'Gradle 7'
jdk 'Java 17'
}
options {
buildDiscarder(logRotator(artifactNumToKeepStr: '20'))
}
stages {
stage ('Build') {
steps {
sh 'git submodule update --init --recursive'
rtGradleRun(
usesPlugin: true,
tool: 'Gradle 7',
buildFile: 'build.gradle.kts',
tasks: 'clean build',
)
}
post {
success {
archiveArtifacts artifacts: 'bootstrap/**/build/libs/Geyser-*.jar', fingerprint: true
}
}
}
}
}

View file

@ -1,6 +1,6 @@
The MIT License The MIT License
Copyright (c) 2019-2023 GeyserMC. http://geysermc.org Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
Permission is hereby granted, free of charge, to any person obtaining a copy 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

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.19.80 - 1.20 and Minecraft Java 1.20. ### Currently supporting Minecraft Bedrock 1.20.80 - 1.21.2 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

@ -25,6 +25,8 @@
package org.geysermc.geyser.processor; package org.geysermc.geyser.processor;
import org.checkerframework.checker.nullness.qual.Nullable;
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.RoundEnvironment;
@ -159,7 +161,7 @@ public class ClassProcessor extends AbstractProcessor {
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Completed processing for " + this.annotationClassName); this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Completed processing for " + this.annotationClassName);
} }
private BufferedReader createReader() throws IOException { private @Nullable BufferedReader createReader() throws IOException {
if (this.outputPath != null) { if (this.outputPath != null) {
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Reading existing " + this.annotationClassName + " list from " + this.outputPath); this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Reading existing " + this.annotationClassName + " list from " + this.outputPath);
return Files.newBufferedReader(this.outputPath); return Files.newBufferedReader(this.outputPath);

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

@ -30,12 +30,14 @@ 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.api.util.ApiVersion; import org.geysermc.api.util.ApiVersion;
import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.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;
@ -78,6 +80,7 @@ public interface GeyserApi extends GeyserApiBase {
* @param apiClass the builder class * @param apiClass the builder class
* @param <R> the implementation type * @param <R> the implementation type
* @param <T> the API type * @param <T> the API type
* @throws IllegalArgumentException if there is no provider for the specified API class
* @return the builder instance * @return the builder instance
*/ */
@NonNull @NonNull
@ -134,6 +137,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.
* *
@ -150,6 +177,6 @@ public interface GeyserApi extends GeyserApiBase {
* @return the current geyser api version * @return the current geyser api version
*/ */
default ApiVersion geyserApiVersion() { default ApiVersion geyserApiVersion() {
return new ApiVersion(2, 1, 1); return new ApiVersion(2, 4, 1);
} }
} }

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

@ -23,11 +23,12 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.item.type; package org.geysermc.geyser.api.bedrock.camera;
// If blocks are implemented, then this class is not needed. /**
public class FlowerItem extends BlockItem { * Represents a camera shake instruction. Can be sent in {@link CameraData#shakeCamera(float, float, CameraShake)}
public FlowerItem(String javaIdentifier, Builder builder) { */
super(javaIdentifier, builder); public enum CameraShake {
} POSITIONAL,
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

@ -0,0 +1,143 @@
/*
* 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.api.block.custom;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
import org.geysermc.geyser.api.block.custom.property.CustomBlockProperty;
import org.geysermc.geyser.api.util.CreativeCategory;
import java.util.List;
import java.util.Map;
/**
* This class is used to store data for a custom block.
*/
public interface CustomBlockData {
/**
* Gets the name of the custom block
*
* @return The name of the custom block.
*/
@NonNull String name();
/**
* Gets the identifier of the custom block
*
* @return The identifier of the custom block.
*/
@NonNull String identifier();
/**
* Gets if the custom block is included in the creative inventory
*
* @return If the custom block is included in the creative inventory.
*/
boolean includedInCreativeInventory();
/**
* Gets the block's creative category, or tab id.
*
* @return the block's creative category
*/
@Nullable CreativeCategory creativeCategory();
/**
* Gets the block's creative group.
*
* @return the block's creative group
*/
@Nullable String creativeGroup();
/**
* Gets the components of the custom block
*
* @return The components of the custom block.
*/
@Nullable CustomBlockComponents components();
/**
* Gets the custom block's map of block property names to CustomBlockProperty
* objects
*
* @return The custom block's map of block property names to CustomBlockProperty objects.
*/
@NonNull Map<String, CustomBlockProperty<?>> properties();
/**
* Gets the list of the custom block's permutations
*
* @return The permutations of the custom block.
*/
@NonNull List<CustomBlockPermutation> permutations();
/**
* Gets the custom block's default block state
*
* @return The default block state of the custom block.
*/
@NonNull CustomBlockState defaultBlockState();
/**
* Gets a builder for a custom block state
*
* @return The builder for a custom block state.
*/
CustomBlockState.@NonNull Builder blockStateBuilder();
/**
* Create a Builder for CustomBlockData
*
* @return A CustomBlockData Builder
*/
static CustomBlockData.Builder builder() {
return GeyserApi.api().provider(CustomBlockData.Builder.class);
}
interface Builder {
Builder name(@NonNull String name);
Builder includedInCreativeInventory(boolean includedInCreativeInventory);
Builder creativeCategory(@Nullable CreativeCategory creativeCategory);
Builder creativeGroup(@Nullable String creativeGroup);
Builder components(@NonNull CustomBlockComponents components);
Builder booleanProperty(@NonNull String propertyName);
Builder intProperty(@NonNull String propertyName, List<Integer> values);
Builder stringProperty(@NonNull String propertyName, List<String> values);
Builder permutations(@NonNull List<CustomBlockPermutation> permutations);
CustomBlockData build();
}
}

View file

@ -23,12 +23,17 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.api.block.custom;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
/** /**
* Contains useful collections for use in Geyser. * This class is used to store a custom block permutations, which contain custom
* block components mapped to a Molang query that should return true or false
* *
* Of note are the fixed int maps. Designed for use with block states that are positive and sequential, they do not allow keys to be * @param components The components of the block
* added that are not greater by one versus the previous key. Because of this, speedy operations of {@link java.util.Map#get(java.lang.Object)} * @param condition The Molang query that should return true or false
* and {@link java.util.Map#containsKey(java.lang.Object)} can be performed by simply checking the bounds of the map
* size and its "start" integer.
*/ */
package org.geysermc.geyser.util.collection; public record CustomBlockPermutation(@NonNull CustomBlockComponents components, @NonNull String condition) {
}

View file

@ -0,0 +1,75 @@
/*
* 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.api.block.custom;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Map;
/**
* This class is used to store a custom block state, which contains CustomBlockData
* tied to defined properties and values
*/
public interface CustomBlockState {
/**
* Gets the custom block data associated with the state
*
* @return The custom block data for the state.
*/
@NonNull CustomBlockData block();
/**
* Gets the name of the state
*
* @return The name of the state.
*/
@NonNull String name();
/**
* Gets the given property for the state
*
* @param propertyName the property name
* @return the boolean, int, or string property.
*/
@NonNull <T> T property(@NonNull String propertyName);
/**
* Gets a map of the properties for the state
*
* @return The properties for the state.
*/
@NonNull Map<String, Object> properties();
interface Builder {
Builder booleanProperty(@NonNull String propertyName, boolean value);
Builder intProperty(@NonNull String propertyName, int value);
Builder stringProperty(@NonNull String propertyName, @NonNull String value);
CustomBlockState build();
}
}

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.api.block.custom;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
import org.geysermc.geyser.api.util.CreativeCategory;
import java.util.List;
/**
* Represents a completely custom block that is not based on an existing vanilla Minecraft block.
*/
public interface NonVanillaCustomBlockData extends CustomBlockData {
/**
* Gets the namespace of the custom block
*
* @return The namespace of the custom block.
*/
@NonNull String namespace();
/**
* Create a Builder for NonVanillaCustomBlockData
*
* @return A NonVanillaCustomBlockData Builder
*/
static NonVanillaCustomBlockData.Builder builder() {
return GeyserApi.api().provider(NonVanillaCustomBlockData.Builder.class);
}
interface Builder extends CustomBlockData.Builder {
Builder namespace(@NonNull String namespace);
@Override
Builder name(@NonNull String name);
@Override
Builder includedInCreativeInventory(boolean includedInCreativeInventory);
@Override
Builder creativeCategory(@Nullable CreativeCategory creativeCategory);
@Override
Builder creativeGroup(@Nullable String creativeGroup);
@Override
Builder components(@NonNull CustomBlockComponents components);
@Override
Builder booleanProperty(@NonNull String propertyName);
@Override
Builder intProperty(@NonNull String propertyName, List<Integer> values);
@Override
Builder stringProperty(@NonNull String propertyName, List<String> values);
@Override
Builder permutations(@NonNull List<CustomBlockPermutation> permutations);
@Override
NonVanillaCustomBlockData build();
}
}

View file

@ -23,42 +23,47 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.util; package org.geysermc.geyser.api.block.custom.component;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import javax.annotation.Nonnull;
import java.util.Iterator;
public final class JavaCodecUtil {
/** /**
* Iterate over a Java Edition codec and return each entry as a CompoundTag * This class is used to store a box component for the selection and
* collision boxes of a custom block.
*
* @param originX The origin X of the box
* @param originY The origin Y of the box
* @param originZ The origin Z of the box
* @param sizeX The size X of the box
* @param sizeY The size Y of the box
* @param sizeZ The size Z of the box
*/ */
public static Iterable<CompoundTag> iterateAsTag(CompoundTag tag) { public record BoxComponent(float originX, float originY, float originZ, float sizeX, float sizeY, float sizeZ) {
ListTag value = tag.get("value"); private static final BoxComponent FULL_BOX = new BoxComponent(-8, 0, -8, 16, 16, 16);
Iterator<Tag> originalIterator = value.iterator(); private static final BoxComponent EMPTY_BOX = new BoxComponent(0, 0, 0, 0, 0, 0);
return new Iterable<>() {
@Nonnull /**
@Override * Gets a full box component
public Iterator<CompoundTag> iterator() { *
return new Iterator<>() { * @return A full box component
@Override */
public boolean hasNext() { public static BoxComponent fullBox() {
return originalIterator.hasNext(); return FULL_BOX;
} }
@Override /**
public CompoundTag next() { * Gets an empty box component
return (CompoundTag) originalIterator.next(); *
} * @return An empty box component
}; */
} public static BoxComponent emptyBox() {
}; return EMPTY_BOX;
} }
private JavaCodecUtil() { /**
* Gets if the box component is empty
*
* @return If the box component is empty.
*/
public boolean isEmpty() {
return sizeX == 0 && sizeY == 0 && sizeZ == 0;
} }
} }

View file

@ -0,0 +1,199 @@
/*
* 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.api.block.custom.component;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class is used to store components for a custom block or custom block permutation.
*/
public interface CustomBlockComponents {
/**
* Gets the selection box component
* Equivalent to "minecraft:selection_box"
*
* @return The selection box.
*/
@Nullable BoxComponent selectionBox();
/**
* Gets the collision box component
* Equivalent to "minecraft:collision_box"
* @return The collision box.
*/
@Nullable BoxComponent collisionBox();
/**
* Gets the display name component
* Equivalent to "minecraft:display_name"
*
* @return The display name.
*/
@Nullable String displayName();
/**
* Gets the geometry component
* Equivalent to "minecraft:geometry"
*
* @return The geometry.
*/
@Nullable GeometryComponent geometry();
/**
* Gets the material instances component
* Equivalent to "minecraft:material_instances"
*
* @return The material instances.
*/
@NonNull Map<String, MaterialInstance> materialInstances();
/**
* Gets the placement filter component
* Equivalent to "minecraft:placement_filter"
*
* @return The placement filter.
*/
@Nullable List<PlacementConditions> placementFilter();
/**
* Gets the destructible by mining component
* Equivalent to "minecraft:destructible_by_mining"
*
* @return The destructible by mining value.
*/
@Nullable Float destructibleByMining();
/**
* Gets the friction component
* Equivalent to "minecraft:friction"
*
* @return The friction value.
*/
@Nullable Float friction();
/**
* Gets the light emission component
* Equivalent to "minecraft:light_emission"
*
* @return The light emission value.
*/
@Nullable Integer lightEmission();
/**
* Gets the light dampening component
* Equivalent to "minecraft:light_dampening"
*
* @return The light dampening value.
*/
@Nullable Integer lightDampening();
/**
* Gets the transformation component
* Equivalent to "minecraft:transformation"
*
* @return The transformation.
*/
@Nullable TransformationComponent transformation();
/**
* Gets the unit cube component
* Equivalent to "minecraft:unit_cube"
*
* @deprecated Use {@link #geometry()} and compare with `minecraft:geometry.full_block` instead.
*
* @return The rotation.
*/
@Deprecated
boolean unitCube();
/**
* Gets if the block should place only air
* Equivalent to setting a dummy event to run on "minecraft:on_player_placing"
*
* @return If the block should place only air.
*/
boolean placeAir();
/**
* Gets the set of tags
* Equivalent to "tag:some_tag"
*
* @return The set of tags.
*/
@NonNull Set<String> tags();
/**
* Create a Builder for CustomBlockComponents
*
* @return A CustomBlockComponents Builder
*/
static CustomBlockComponents.Builder builder() {
return GeyserApi.api().provider(CustomBlockComponents.Builder.class);
}
interface Builder {
Builder selectionBox(BoxComponent selectionBox);
Builder collisionBox(BoxComponent collisionBox);
Builder displayName(String displayName);
Builder geometry(GeometryComponent geometry);
Builder materialInstance(@NonNull String name, @NonNull MaterialInstance materialInstance);
Builder placementFilter(List<PlacementConditions> placementConditions);
Builder destructibleByMining(Float destructibleByMining);
Builder friction(Float friction);
Builder lightEmission(Integer lightEmission);
Builder lightDampening(Integer lightDampening);
Builder transformation(TransformationComponent transformation);
/**
* @deprecated Use {@link #geometry(GeometryComponent)} with `minecraft:geometry.full_block` instead.
*/
@Deprecated
Builder unitCube(boolean unitCube);
Builder placeAir(boolean placeAir);
Builder tags(@Nullable Set<String> tags);
CustomBlockComponents build();
}
}

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,37 +23,47 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.platform.sponge.command; package org.geysermc.geyser.api.block.custom.component;
import lombok.AllArgsConstructor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.command.GeyserCommandSource; import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.api.command.CommandCause; import org.geysermc.geyser.api.GeyserApi;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
@AllArgsConstructor import java.util.Map;
public class SpongeCommandSource implements GeyserCommandSource {
private final CommandCause handle; /**
* This class is used to store data for a geometry component.
*/
public interface GeometryComponent {
@Override /**
public String name() { * Gets the identifier of the geometry
return handle.friendlyIdentifier().orElse(handle.identifier()); *
* @return The identifier of the geometry.
*/
@NonNull String identifier();
/**
* Gets the bone visibility of the geometry
*
* @return The bone visibility of the geometry.
*/
@Nullable Map<String, String> boneVisibility();
/**
* Creates a builder for GeometryComponent
*
* @return a builder for GeometryComponent.
*/
static GeometryComponent.Builder builder() {
return GeyserApi.api().provider(GeometryComponent.Builder.class);
} }
@Override interface Builder {
public void sendMessage(@NonNull String message) { Builder identifier(@NonNull String identifier);
handle.audience().sendMessage(LegacyComponentSerializer.legacySection().deserialize(message));
}
@Override Builder boneVisibility(@Nullable Map<String, String> boneVisibility);
public boolean isConsole() {
return !(handle.cause().root() instanceof ServerPlayer);
}
@Override GeometryComponent build();
public boolean hasPermission(String permission) {
return handle.hasPermission(permission);
} }
} }

View file

@ -0,0 +1,83 @@
/*
* 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.api.block.custom.component;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
/**
* This class is used to store data for a material instance.
*/
public interface MaterialInstance {
/**
* Gets the texture of the block
*
* @return The texture of the block.
*/
@Nullable String texture();
/**
* Gets the render method of the block
*
* @return The render method of the block.
*/
@Nullable String renderMethod();
/**
* Gets if the block should be dimmed on certain faces
*
* @return If the block should be dimmed on certain faces.
*/
boolean faceDimming();
/**
* Gets if the block should have ambient occlusion
*
* @return If the block should have ambient occlusion.
*/
boolean ambientOcclusion();
/**
* Creates a builder for MaterialInstance.
*
* @return a builder for MaterialInstance
*/
static MaterialInstance.Builder builder() {
return GeyserApi.api().provider(MaterialInstance.Builder.class);
}
interface Builder {
Builder texture(@Nullable String texture);
Builder renderMethod(@Nullable String renderMethod);
Builder faceDimming(boolean faceDimming);
Builder ambientOcclusion(boolean ambientOcclusion);
MaterialInstance build();
}
}

View file

@ -0,0 +1,53 @@
/*
* 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.api.block.custom.component;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.LinkedHashMap;
import java.util.Set;
/**
* This class is used to store conditions for a placement filter for a custom block.
*
* @param allowedFaces The faces that the block can be placed on
* @param blockFilters The block filters that control what blocks the block can be placed on
*/
public record PlacementConditions(@NonNull Set<Face> allowedFaces, @NonNull LinkedHashMap<String, BlockFilterType> blockFilters) {
public enum Face {
DOWN,
UP,
NORTH,
SOUTH,
WEST,
EAST
}
public enum BlockFilterType {
BLOCK,
TAG
}
}

View file

@ -0,0 +1,67 @@
/*
* 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.api.block.custom.component;
/**
* This class is used to store the transformation component of a block
*
* @param rx The rotation on the x axis
* @param ry The rotation on the y axis
* @param rz The rotation on the z axis
* @param sx The scale on the x axis
* @param sy The scale on the y axis
* @param sz The scale on the z axis
* @param tx The translation on the x axis
* @param ty The translation on the y axis
* @param tz The translation on the z axis
*/
public record TransformationComponent(int rx, int ry, int rz, float sx, float sy, float sz, float tx, float ty, float tz) {
/**
* Constructs a new TransformationComponent with the rotation values and assumes default scale and translation
*
* @param rx The rotation on the x axis
* @param ry The rotation on the y axis
* @param rz The rotation on the z axis
*/
public TransformationComponent(int rx, int ry, int rz) {
this(rx, ry, rz, 1, 1, 1, 0, 0, 0);
}
/**
* Constructs a new TransformationComponent with the rotation and scale values and assumes default translation
*
* @param rx The rotation on the x axis
* @param ry The rotation on the y axis
* @param rz The rotation on the z axis
* @param sx The scale on the x axis
* @param sy The scale on the y axis
* @param sz The scale on the z axis
*/
public TransformationComponent(int rx, int ry, int rz, float sx, float sy, float sz) {
this(rx, ry, rz, sx, sy, sz, 0, 0, 0);
}
}

View file

@ -0,0 +1,7 @@
package org.geysermc.geyser.api.block.custom.nonvanilla;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
public record JavaBlockItem(@NonNull String identifier, @NonNegative int javaId, @NonNegative int stackSize) {
}

View file

@ -0,0 +1,119 @@
package org.geysermc.geyser.api.block.custom.nonvanilla;
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.GeyserApi;
public interface JavaBlockState {
/**
* Gets the identifier of the block state
*
* @return the identifier of the block state
*/
@NonNull String identifier();
/**
* Gets the Java ID of the block state
*
* @return the Java ID of the block state
*/
@NonNegative int javaId();
/**
* Gets the state group ID of the block state
*
* @return the state group ID of the block state
*/
@NonNegative int stateGroupId();
/**
* Gets the block hardness of the block state
*
* @return the block hardness of the block state
*/
@NonNegative float blockHardness();
/**
* Gets whether the block state is waterlogged
*
* @return whether the block state is waterlogged
*/
boolean waterlogged();
/**
* Gets the collision of the block state
*
* @return the collision of the block state
*/
@NonNull JavaBoundingBox[] collision();
/**
* Gets whether the block state can be broken with hand
*
* @return whether the block state can be broken with hand
*/
boolean canBreakWithHand();
/**
* Gets the pick item of the block state
*
* @return the pick item of the block state
*/
@Nullable String pickItem();
/**
* Gets the piston behavior of the block state
*
* @return the piston behavior of the block state
*/
@Nullable String pistonBehavior();
/**
* Gets whether the block state has block entity
*
* @return whether the block state has block entity
* @deprecated Does not have an effect. If you were using this to
* set piston behavior, use {@link #pistonBehavior()} instead.
*/
@Deprecated(forRemoval = true)
boolean hasBlockEntity();
/**
* Creates a new {@link JavaBlockState.Builder} instance
*
* @return a new {@link JavaBlockState.Builder} instance
*/
static JavaBlockState.Builder builder() {
return GeyserApi.api().provider(JavaBlockState.Builder.class);
}
interface Builder {
Builder identifier(@NonNull String identifier);
Builder javaId(@NonNegative int javaId);
Builder stateGroupId(@NonNegative int stateGroupId);
Builder blockHardness(@NonNegative float blockHardness);
Builder waterlogged(boolean waterlogged);
Builder collision(@NonNull JavaBoundingBox[] collision);
Builder canBreakWithHand(boolean canBreakWithHand);
Builder pickItem(@Nullable String pickItem);
Builder pistonBehavior(@Nullable String pistonBehavior);
/**
* @deprecated Does not have an effect. If you were using this to
* * set piston behavior, use {@link #pistonBehavior(String)} instead.
*/
@Deprecated(forRemoval = true)
Builder hasBlockEntity(boolean hasBlockEntity);
JavaBlockState build();
}
}

View file

@ -0,0 +1,4 @@
package org.geysermc.geyser.api.block.custom.nonvanilla;
public record JavaBoundingBox(double middleX, double middleY, double middleZ, double sizeX, double sizeY, double sizeZ) {
}

View file

@ -0,0 +1,56 @@
/*
* 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.api.block.custom.property;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
/**
* This class is used to store a property of a custom block of a generic type.
*/
public interface CustomBlockProperty<T> {
/**
* Gets the name of the property
*
* @return The name of the property.
*/
@NonNull String name();
/**
* Gets the values of the property
*
* @return The values of the property.
*/
@NonNull List<T> values();
/**
* Gets the type of the property
*
* @return The type of the property.
*/
@NonNull PropertyType type();
}

View file

@ -0,0 +1,79 @@
/*
* 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.api.block.custom.property;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* This class is used to define a custom block property's type.
*/
public class PropertyType {
private static final PropertyType BOOLEAN = new PropertyType(Boolean.class);
private static final PropertyType INTEGER = new PropertyType(Integer.class);
private static final PropertyType STRING = new PropertyType(String.class);
/**
* Gets the property type for a boolean.
*
* @return The property type for a boolean.
*/
@NonNull public static PropertyType booleanProp() {
return BOOLEAN;
}
/**
* Gets the property type for an integer.
*
* @return The property type for an integer.
*/
@NonNull public static PropertyType integerProp() {
return INTEGER;
}
/**
* Gets the property type for a string.
*
* @return The property type for a string.
*/
@NonNull public static PropertyType stringProp() {
return STRING;
}
private final Class<?> typeClass;
/**
* Gets the class of the property type
*
* @return The class of the property type.
*/
@NonNull public Class<?> typeClass() {
return typeClass;
}
private PropertyType(Class<?> typeClass) {
this.typeClass = typeClass;
}
}

View file

@ -29,27 +29,107 @@ 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.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;
import java.util.Set;
import java.util.concurrent.CompletableFuture; 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);
/** /**
* Displays a player entity as emoting to this client.
* *
* @param emoter the player entity emoting. * @param emoter the player entity emoting.
* @param emoteId the emote ID to send to the 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.
* <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
*
* @deprecated Use {@link CameraData#shakeCamera(float, float, CameraShake)} instead.
*/
@Deprecated
void shakeCamera(float intensity, float duration, @NonNull CameraShake type);
/**
* Stops all camera shake of any type.
*
* @deprecated Use {@link CameraData#stopCameraShake()} instead.
*/
@Deprecated
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.
* @deprecated Use {@link CameraData#sendFog(String...)} instead.
*/
@Deprecated
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.
* @deprecated Use {@link CameraData#removeFog(String...)} instead.
*/
@Deprecated
void removeFog(String... fogNameSpaces);
/**
* Returns an immutable copy of all fog affects currently applied to this client.
*
* @deprecated Use {@link CameraData#fogEffects()} instead.
*/
@Deprecated
@NonNull
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

@ -28,7 +28,6 @@ package org.geysermc.geyser.api.event;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.Event; import org.geysermc.event.Event;
import org.geysermc.event.bus.OwnedEventBus; import org.geysermc.event.bus.OwnedEventBus;
import org.geysermc.geyser.api.extension.Extension;
import java.util.Set; import java.util.Set;

View file

@ -27,7 +27,6 @@ package org.geysermc.geyser.api.event;
import org.geysermc.event.Event; import org.geysermc.event.Event;
import org.geysermc.event.subscribe.OwnedSubscriber; import org.geysermc.event.subscribe.OwnedSubscriber;
import org.geysermc.geyser.api.extension.Extension;
/** /**
* Represents a subscribed listener to a {@link Event}. Wraps around * Represents a subscribed listener to a {@link Event}. Wraps around

View file

@ -23,31 +23,39 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.item.type; package org.geysermc.geyser.api.event.bedrock;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
public class ChestItem extends BlockItem { /**
* Called when a Geyser session disconnects.
*/
public class SessionDisconnectEvent extends ConnectionEvent {
private String disconnectReason;
public ChestItem(String javaIdentifier, Builder builder) { public SessionDisconnectEvent(@NonNull GeyserConnection connection, @NonNull String reason) {
super(javaIdentifier, builder); super(connection);
this.disconnectReason = reason;
} }
@Override /**
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) { * Gets the disconnect reason.
super.translateNbtToBedrock(session, tag); *
* @return the reason for the disconnect
*/
public @NonNull String disconnectReason() {
return disconnectReason;
}
// Strip the BlockEntityTag from the chests contents /**
// sent to the client. The client does not parse this * Sets the disconnect reason, thereby overriding th original reason.
// or use it for anything, as this tag is fully *
// server-side, so we remove it to reduce bandwidth and * @param disconnectReason the reason for the disconnect
// solve potential issues with very large tags. */
public void disconnectReason(@NonNull String disconnectReason) {
// There was a problem in the past where this would strip this.disconnectReason = disconnectReason;
// NBT data in creative mode, however with the new server
// authoritative inventories, this is no longer a concern.
tag.remove("BlockEntityTag");
} }
} }

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

@ -34,7 +34,7 @@ import java.net.InetSocketAddress;
/** /**
* Called whenever Geyser gets pinged * Called whenever Geyser gets pinged
* * <p>
* This event allows you to modify/obtain the MOTD, maximum player count, and current number of players online, * This event allows you to modify/obtain the MOTD, maximum player count, and current number of players online,
* Geyser will reply to the client with what was given. * Geyser will reply to the client with what was given.
*/ */

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

@ -33,7 +33,7 @@ import java.util.Map;
/** /**
* Called when commands are defined within Geyser. * Called when commands are defined within Geyser.
* * <p>
* This event allows you to register new commands using the {@link #register(Command)} * This event allows you to register new commands using the {@link #register(Command)}
* method and retrieve the default commands defined. * method and retrieve the default commands defined.
*/ */

View file

@ -0,0 +1,75 @@
/*
* 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.api.event.lifecycle;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.api.block.custom.CustomBlockState;
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState;
import org.geysermc.event.Event;
/**
* Called on Geyser's startup when looking for custom blocks. Custom blocks must be registered through this event.
* <p>
* This event will not be called if the "add-non-bedrock-items" setting is disabled in the Geyser config.
*/
public abstract class GeyserDefineCustomBlocksEvent implements Event {
/**
* Registers the given {@link CustomBlockData} as a custom block
*
* @param customBlockData the custom block to register
*/
public abstract void register(@NonNull CustomBlockData customBlockData);
/**
* Registers the given {@link CustomBlockState} as an override for the
* given java state identifier
* Java state identifiers are listed
* <a href="https://raw.githubusercontent.com/GeyserMC/mappings/master/blocks.json">here</a>
*
* @param javaIdentifier the java state identifier to override
* @param customBlockState the custom block state with which to override java state identifier
*/
public abstract void registerOverride(@NonNull String javaIdentifier, @NonNull CustomBlockState customBlockState);
/**
* Registers the given {@link CustomBlockData} as an override for the
* given java item identifier
*
* @param javaIdentifier the java item identifier to override
* @param customBlockData the custom block data with which to override java item identifier
*/
public abstract void registerItemOverride(@NonNull String javaIdentifier, @NonNull CustomBlockData customBlockData);
/**
* Registers the given {@link CustomBlockState} as an override for the
* given {@link JavaBlockState}
*
* @param javaBlockState the java block state for the non-vanilla block
* @param customBlockState the custom block state with which to override java state identifier
*/
public abstract void registerOverride(@NonNull JavaBlockState javaBlockState, @NonNull CustomBlockState customBlockState);
}

View file

@ -36,7 +36,7 @@ import java.util.Map;
/** /**
* Called on Geyser's startup when looking for custom items. Custom items must be registered through this event. * Called on Geyser's startup when looking for custom items. Custom items must be registered through this event.
* * <p>
* This event will not be called if the "add non-Bedrock items" setting is disabled in the Geyser config. * This event will not be called if the "add non-Bedrock items" setting is disabled in the Geyser config.
*/ */
public interface GeyserDefineCustomItemsEvent extends Event { public interface GeyserDefineCustomItemsEvent extends Event {

View file

@ -0,0 +1,28 @@
package org.geysermc.geyser.api.event.lifecycle;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.Event;
/**
* Called on Geyser's startup when looking for custom skulls. Custom skulls must be registered through this event.
* <p>
* This event will not be called if the "add-non-bedrock-items" setting is disabled in the Geyser config.
*/
public abstract class GeyserDefineCustomSkullsEvent implements Event {
/**
* The type of texture provided
*/
public enum SkullTextureType {
USERNAME,
UUID,
PROFILE,
SKIN_HASH
}
/**
* Registers the given username, UUID, base64 encoded profile, or skin hash as a custom skull blocks
* @param texture the username, UUID, base64 encoded profile, or skin hash
* @param type the type of texture provided
*/
public abstract void register(@NonNull String texture, @NonNull SkullTextureType type);
}

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

@ -36,13 +36,13 @@ import java.util.Collection;
public abstract class ExtensionManager { public abstract class ExtensionManager {
/** /**
* Gets an extension with the given name. * Gets an extension by the given ID.
* *
* @param name the name of the extension * @param id the ID of the extension
* @return an extension with the given name * @return an extension with the given ID
*/ */
@Nullable @Nullable
public abstract Extension extension(@NonNull String name); public abstract Extension extension(@NonNull String id);
/** /**
* Enables the given {@link Extension}. * Enables the given {@link Extension}.

View file

@ -25,10 +25,16 @@
package org.geysermc.geyser.api.extension.exception; package org.geysermc.geyser.api.extension.exception;
import java.io.Serial;
/** /**
* Thrown when an extension's description is invalid. * Thrown when an extension's description is invalid.
*/ */
public class InvalidDescriptionException extends Exception { public class InvalidDescriptionException extends Exception {
@Serial
private static final long serialVersionUID = 1L;
public InvalidDescriptionException(Throwable cause) { public InvalidDescriptionException(Throwable cause) {
super(cause); super(cause);
} }

View file

@ -25,10 +25,16 @@
package org.geysermc.geyser.api.extension.exception; package org.geysermc.geyser.api.extension.exception;
import java.io.Serial;
/** /**
* Thrown when an extension is invalid. * Thrown when an extension is invalid.
*/ */
public class InvalidExtensionException extends Exception { public class InvalidExtensionException extends Exception {
@Serial
private static final long serialVersionUID = 1L;
public InvalidExtensionException(Throwable cause) { public InvalidExtensionException(Throwable cause) {
super(cause); super(cause);
} }

View file

@ -29,6 +29,9 @@ 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;
/** /**
* This is used to store data for a custom item. * This is used to store data for a custom item.
*/ */
@ -75,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.
* *
@ -89,6 +106,14 @@ public interface CustomItemData {
*/ */
@Nullable CustomRenderOffsets renderOffsets(); @Nullable CustomRenderOffsets renderOffsets();
/**
* Gets the item's set of tags that can be used in Molang.
* Equivalent to "tag:some_tag"
*
* @return the item's tags, if they exist
*/
@NonNull Set<String> tags();
static CustomItemData.Builder builder() { static CustomItemData.Builder builder() {
return GeyserApi.api().provider(CustomItemData.Builder.class); return GeyserApi.api().provider(CustomItemData.Builder.class);
} }
@ -109,10 +134,16 @@ 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);
Builder tags(@Nullable Set<String> tags);
CustomItemData build(); CustomItemData build();
} }
} }

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);
@ -239,6 +245,9 @@ public interface NonVanillaCustomItemData extends CustomItemData {
@Override @Override
Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets); Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets);
@Override
Builder tags(@Nullable Set<String> tags);
NonVanillaCustomItemData build(); NonVanillaCustomItemData build();
} }
} }

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

@ -67,4 +67,11 @@ public interface RemoteServer {
*/ */
@NonNull @NonNull
AuthType authType(); AuthType authType();
/**
* Gets if we should attempt to resolve the SRV record for this server.
*
* @return if we should attempt to resolve the SRV record for this server
*/
boolean resolveSrv();
} }

View file

@ -0,0 +1,40 @@
/*
* 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 cape.
*
* @param textureUrl The URL of the cape texture
* @param capeId The ID of the cape
* @param capeData The raw cape image data in ARGB format
* @param failed If the cape failed to load, this is for things like fallback capes
*/
public record Cape(String textureUrl, String capeId, byte[] capeData, boolean failed) {
public Cape(String textureUrl, String capeId, byte[] capeData) {
this(textureUrl, capeId, capeData, false);
}
}

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,16 +23,17 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.translator.collision; package org.geysermc.geyser.api.skin;
import lombok.EqualsAndHashCode; /**
* Represents a skin.
@EqualsAndHashCode(callSuper = true) *
@CollisionRemapper(regex = "^spawner$") * @param textureUrl The URL/ID of the skin texture
public class SpawnerCollision extends SolidCollision { * @param skinData The raw skin image data in ARGB
public SpawnerCollision(String params) { * @param failed If the skin failed to load, this is for things like fallback skins
super(params); */
// Increase pushAwayTolerance to work around https://bugs.mojang.com/browse/MCPE-41996 public record Skin(String textureUrl, byte[] skinData, boolean failed) {
pushAwayTolerance = 0.0002; 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

@ -0,0 +1,65 @@
/*
* 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.util;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Represents the creative menu categories or tabs.
*/
public enum CreativeCategory {
CONSTRUCTION("construction", 1),
NATURE("nature", 2),
EQUIPMENT("equipment", 3),
ITEMS("items", 4),
NONE("none", 6);
private final String internalName;
private final int id;
CreativeCategory(String internalName, int id) {
this.internalName = internalName;
this.id = id;
}
/**
* Gets the internal name of the category.
*
* @return the name of the category
*/
public @NonNull String internalName() {
return internalName;
}
/**
* Gets the internal ID of the category.
*
* @return the ID of the category
*/
public int id() {
return id;
}
}

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

@ -29,11 +29,17 @@ package org.geysermc.geyser.api.util;
* Represents the platform Geyser is running on. * Represents the platform Geyser is running on.
*/ */
public record PlatformType(String platformName) { public record PlatformType(String platformName) {
public static PlatformType ANDROID = new PlatformType("Android");
public static PlatformType BUNGEECORD = new PlatformType("BungeeCord"); @Deprecated
public static PlatformType FABRIC = new PlatformType("Fabric"); public static final PlatformType ANDROID = new PlatformType("Android");
public static PlatformType SPIGOT = new PlatformType("Spigot"); public static final PlatformType BUNGEECORD = new PlatformType("BungeeCord");
public static PlatformType SPONGE = new PlatformType("Sponge"); public static final PlatformType FABRIC = new PlatformType("Fabric");
public static PlatformType STANDALONE = new PlatformType("Standalone"); public static final PlatformType NEOFORGE = new PlatformType("NeoForge");
public static PlatformType VELOCITY = new PlatformType("Velocity"); public static final PlatformType SPIGOT = new PlatformType("Spigot");
@Deprecated
public static final PlatformType SPONGE = new PlatformType("Sponge");
public static final PlatformType STANDALONE = new PlatformType("Standalone");
public static final PlatformType VELOCITY = new PlatformType("Velocity");
public static final PlatformType VIAPROXY = new PlatformType("ViaProxy");
} }

View file

@ -30,7 +30,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* This is a way to represent a boolean, but with a non set value added. * This is a way to represent a boolean, but with a non set value added.
* This class was inspired by adventure's version https://github.com/KyoriPowered/adventure/blob/main/4/api/src/main/java/net/kyori/adventure/util/TriState.java * This class was inspired by adventure's <a href="https://github.com/KyoriPowered/adventure/blob/main/4/api/src/main/java/net/kyori/adventure/util/TriState.java">TriState</a>
*/ */
public enum TriState { public enum TriState {
/** /**

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

@ -32,18 +32,26 @@ import net.md_5.bungee.protocol.packet.LoginSuccess;
import net.md_5.bungee.protocol.packet.SetCompression; import net.md_5.bungee.protocol.packet.SetCompression;
public class GeyserBungeeCompressionDisabler extends ChannelOutboundHandlerAdapter { public class GeyserBungeeCompressionDisabler extends ChannelOutboundHandlerAdapter {
private boolean compressionDisabled = false;
@Override @Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (!(msg instanceof SetCompression)) { if (!(msg instanceof SetCompression)) {
if (msg instanceof LoginSuccess) { // Fixes https://github.com/GeyserMC/Geyser/issues/4281
// We're past the point that compression can be enabled // The server may send a LoginDisconnect packet after compression is set.
if (!compressionDisabled) {
if (ctx.pipeline().get("compress") != null) { if (ctx.pipeline().get("compress") != null) {
ctx.pipeline().remove("compress"); ctx.pipeline().remove("compress");
compressionDisabled = true;
} }
if (ctx.pipeline().get("decompress") != null) { if (ctx.pipeline().get("decompress") != null) {
ctx.pipeline().remove("decompress"); ctx.pipeline().remove("decompress");
compressionDisabled = true;
} }
}
if (msg instanceof LoginSuccess) {
// We're past the point that compression can be enabled
ctx.pipeline().remove(this); ctx.pipeline().remove(this);
} }
super.write(ctx, msg, promise); super.write(ctx, msg, promise);

View file

@ -30,6 +30,7 @@ import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.dump.BootstrapDumpInfo;
import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -51,7 +52,8 @@ public class GeyserBungeeDumpInfo extends BootstrapDumpInfo {
this.plugins = new ArrayList<>(); this.plugins = new ArrayList<>();
for (net.md_5.bungee.api.config.ListenerInfo listener : proxy.getConfig().getListeners()) { for (net.md_5.bungee.api.config.ListenerInfo listener : proxy.getConfig().getListeners()) {
this.listeners.add(new ListenerInfo(listener.getHost().getHostString(), listener.getHost().getPort())); InetSocketAddress address = (InetSocketAddress) listener.getSocketAddress();
this.listeners.add(new ListenerInfo(address.getHostString(), address.getPort()));
} }
for (Plugin plugin : proxy.getPluginManager().getPlugins()) { for (Plugin plugin : proxy.getPluginManager().getPlugins()) {

View file

@ -39,6 +39,7 @@ import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.netty.PipelineUtils; import net.md_5.bungee.netty.PipelineUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.network.netty.GeyserInjector; import org.geysermc.geyser.network.netty.GeyserInjector;
@ -125,7 +126,7 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener {
.channel(LocalServerChannelWrapper.class) .channel(LocalServerChannelWrapper.class)
.childHandler(new ChannelInitializer<>() { .childHandler(new ChannelInitializer<>() {
@Override @Override
protected void initChannel(Channel ch) throws Exception { protected void initChannel(@NonNull Channel ch) throws Exception {
if (proxy.getConfig().getServers() == null) { if (proxy.getConfig().getServers() == null) {
// Proxy hasn't finished loading all plugins - it loads the config after all plugins // Proxy hasn't finished loading all plugins - it loads the config after all plugins
// Probably doesn't need to be translatable? // Probably doesn't need to be translatable?

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

@ -35,12 +35,12 @@ import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.ProxyPingEvent; import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.protocol.ProtocolConstants; import net.md_5.bungee.protocol.ProtocolConstants;
import org.checkerframework.checker.nullness.qual.Nullable;
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 java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -61,16 +61,11 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
})); }));
ProxyPingEvent event = future.join(); ProxyPingEvent event = future.join();
ServerPing response = event.getResponse(); ServerPing response = event.getResponse();
GeyserPingInfo geyserPingInfo = new GeyserPingInfo( return new GeyserPingInfo(
response.getDescriptionComponent().toLegacyText(), response.getDescriptionComponent().toLegacyText(),
new GeyserPingInfo.Players(response.getPlayers().getMax(), response.getPlayers().getOnline()), response.getPlayers().getMax(),
new GeyserPingInfo.Version(response.getVersion().getName(), response.getVersion().getProtocol()) response.getPlayers().getOnline()
); );
if (event.getResponse().getPlayers().getSample() != null) {
Arrays.stream(event.getResponse().getPlayers().getSample()).forEach(proxiedPlayer ->
geyserPingInfo.getPlayerList().add(proxiedPlayer.getName()));
}
return geyserPingInfo;
} }
// This is static so pending connection can use it // This is static so pending connection can use it
@ -110,7 +105,7 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
} }
@Override @Override
public InetSocketAddress getVirtualHost() { public @Nullable InetSocketAddress getVirtualHost() {
return null; return null;
} }

View file

@ -30,12 +30,13 @@ import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.plugin.Plugin; 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.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;
@ -44,7 +45,6 @@ import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandExecutor; import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandExecutor;
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 org.jetbrains.annotations.NotNull;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -58,65 +58,97 @@ 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;
@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_19_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() {
// Remove this in like a year // Big hack - Bungee does not provide us an event to listen to, so schedule a repeating
if (getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) { // task that waits for a field to be filled which is set after the plugin enable
geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/")); // 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; 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()) {
if (info.isQueryEnabled() && info.getQueryPort() == geyserConfig.getBedrock().port()) { if (info.isQueryEnabled() && info.getQueryPort() == geyserConfig.getBedrock().port()) {
@ -135,48 +167,21 @@ 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 (geyserConfig.isLegacyPingPassthrough()) {
this.awaitStartupCompletion(0); this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else {
this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy());
} }
@SuppressWarnings("unchecked") // No need to re-register commands or re-init injector when reloading
private void awaitStartupCompletion(int tries) { if (GeyserImpl.getInstance().isReloading()) {
// 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.postStartup();
return; 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::postStartup, tries, TimeUnit.SECONDS);
} else {
this.awaitStartupCompletion(++tries);
}
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
}
}
private void postStartup() {
GeyserImpl.start();
this.geyserInjector = new GeyserBungeeInjector(this);
this.geyserInjector.initializeLocalChannel(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()) {
Map<String, Command> commands = entry.getValue(); Map<String, Command> commands = entry.getValue();
@ -186,16 +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()) { @Override
this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); public void onGeyserDisable() {
} else { if (geyser != null) {
this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy()); geyser.disable();
} }
} }
@Override @Override
public void onDisable() { public void onGeyserShutdown() {
if (geyser != null) { if (geyser != null) {
geyser.shutdown(); geyser.shutdown();
} }
@ -204,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;
@ -245,7 +256,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
return this.geyserInjector.getServerSocketAddress(); return this.geyserInjector.getServerSocketAddress();
} }
@NotNull @NonNull
@Override @Override
public String getServerBindAddress() { public String getServerBindAddress() {
return findCompatibleListener().map(InetSocketAddress::getHostString).orElse(""); return findCompatibleListener().map(InetSocketAddress::getHostString).orElse("");
@ -271,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

@ -29,6 +29,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
@ -50,7 +51,7 @@ public class BungeeCommandSource implements GeyserCommandSource {
} }
@Override @Override
public void sendMessage(String message) { public void sendMessage(@NonNull String message) {
handle.sendMessage(TextComponent.fromLegacyText(message)); handle.sendMessage(TextComponent.fromLegacyText(message));
} }

View file

@ -1,125 +0,0 @@
import net.fabricmc.loom.task.RemapJarTask
plugins {
id("fabric-loom") version "1.0-SNAPSHOT"
id("com.modrinth.minotaur") version "2.+"
}
java {
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_17
}
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")
}
}
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")
loaders.add("fabric")
failSilently.set(true)
dependencies {
required.project("fabric-api")
}
}

View file

@ -1,268 +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;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
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.fabricmc.loader.api.ModContainer;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.server.MinecraftServer;
import org.apache.logging.log4j.LogManager;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.fabric.command.GeyserFabricCommandExecutor;
import org.geysermc.geyser.platform.fabric.world.GeyserFabricWorldManager;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
private static GeyserFabricMod instance;
private boolean reloading;
private GeyserImpl geyser;
private ModContainer mod;
private Path dataFolder;
private MinecraftServer server;
private GeyserCommandManager geyserCommandManager;
private GeyserFabricConfiguration geyserConfig;
private GeyserFabricLogger geyserLogger;
private IGeyserPingPassthrough geyserPingPassthrough;
private WorldManager geyserWorldManager;
@Override
public void onInitialize() {
instance = this;
mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow();
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);
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;
}
this.geyserLogger = new GeyserFabricLogger(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(PlatformType.FABRIC, this);
if (server == null) {
// 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;
}
}
/**
* Initialize core Geyser.
* A function, as it needs to be called in different places depending on if Geyser is being reloaded or not.
*
* @param server The minecraft server.
*/
public void startGeyser(MinecraftServer server) {
this.server = server;
GeyserImpl.start();
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
this.geyserWorldManager = new GeyserFabricWorldManager(server);
// Start command building
// Set just "geyser" as the help command
GeyserFabricCommandExecutor helpExecutor = new GeyserFabricCommandExecutor(geyser,
(GeyserCommand) geyser.commandManager().getCommands().get("help"));
LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal("geyser").executes(helpExecutor);
// Register all subcommands as valid
for (Map.Entry<String, Command> command : geyser.commandManager().getCommands().entrySet()) {
GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(geyser, (GeyserCommand) command.getValue());
builder.then(Commands.literal(command.getKey())
.executes(executor)
// Could also test for Bedrock but depending on when this is called it may backfire
.requires(executor::testPermission));
}
server.getCommands().getDispatcher().register(builder);
}
@Override
public void onDisable() {
if (geyser != null) {
geyser.shutdown();
geyser = null;
}
if (!reloading) {
this.server = null;
}
}
@Override
public GeyserConfiguration getGeyserConfig() {
return geyserConfig;
}
@Override
public GeyserLogger getGeyserLogger() {
return geyserLogger;
}
@Override
public GeyserCommandManager getGeyserCommandManager() {
return geyserCommandManager;
}
@Override
public IGeyserPingPassthrough getGeyserPingPassthrough() {
return geyserPingPassthrough;
}
@Override
public WorldManager getWorldManager() {
return geyserWorldManager;
}
@Override
public Path getConfigFolder() {
return dataFolder;
}
@Override
public BootstrapDumpInfo getDumpInfo() {
return new GeyserFabricDumpInfo(server);
}
@Override
public String getMinecraftServerVersion() {
return this.server.getServerVersion();
}
@SuppressWarnings("ConstantConditions") // IDEA thinks that ip cannot be null
@NotNull
@Override
public String getServerBindAddress() {
String ip = this.server.getLocalIp();
return ip != null ? ip : ""; // See issue #3812
}
@Override
public int getServerPort() {
return ((GeyserServerPortGetter) server).geyser$getServerPort();
}
@Override
public boolean testFloodgatePluginPresent() {
Optional<ModContainer> floodgate = FabricLoader.getInstance().getModContainer("floodgate");
if (floodgate.isPresent()) {
geyserConfig.loadFloodgate(this, floodgate.orElse(null));
return true;
}
return false;
}
@Nullable
@Override
public InputStream getResourceOrNull(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;
}
}
public void setReloading(boolean reloading) {
this.reloading = reloading;
}
public static GeyserFabricMod getInstance() {
return instance;
}
}

View file

@ -1,279 +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.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.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 javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
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;
}
LevelChunk chunk = player.level().getChunk(x, z);
final int chunkBlockX = x << 4;
final int chunkBlockZ = z << 4;
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;
}
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);
}
@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
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(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 = compoundTag.get(key);
tag.accept(visitor);
visitor.root.put(visitor.currentTag);
}
return visitor.root;
}
@Override
public void visitEnd(EndTag endTag) {
}
}
}

View file

@ -1,14 +0,0 @@
{
"required": true,
"package": "org.geysermc.geyser.platform.fabric.mixin",
"compatibilityLevel": "JAVA_16",
"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

@ -43,21 +43,27 @@ import java.util.stream.Collectors;
@Getter @Getter
public class GeyserFabricDumpInfo extends BootstrapDumpInfo { public class GeyserFabricDumpInfo extends BootstrapDumpInfo {
private String platformVersion = null; private final String platformName;
private String platformVersion;
private final String minecraftVersion;
private final EnvType environmentType; private final EnvType environmentType;
@AsteriskSerializer.Asterisk(isIp = true) @AsteriskSerializer.Asterisk(isIp = true)
private final String serverIP; private final String serverIP;
private final int serverPort; private final int serverPort;
private final boolean onlineMode;
private final List<ModInfo> mods; private final List<ModInfo> mods;
public GeyserFabricDumpInfo(MinecraftServer server) { public GeyserFabricDumpInfo(MinecraftServer server) {
this.platformName = server.getServerModName();
FabricLoader.getInstance().getModContainer("fabricloader").ifPresent(mod -> FabricLoader.getInstance().getModContainer("fabricloader").ifPresent(mod ->
this.platformVersion = mod.getMetadata().getVersion().getFriendlyString()); this.platformVersion = mod.getMetadata().getVersion().getFriendlyString());
this.minecraftVersion = server.getServerVersion();
this.environmentType = FabricLoader.getInstance().getEnvironmentType(); this.environmentType = FabricLoader.getInstance().getEnvironmentType();
this.serverIP = server.getLocalIp() == null ? "unknown" : server.getLocalIp(); this.serverIP = server.getLocalIp() == null ? "unknown" : server.getLocalIp();
this.serverPort = server.getPort(); this.serverPort = server.getPort();
this.onlineMode = server.usesAuthentication();
this.mods = new ArrayList<>(); this.mods = new ArrayList<>();
for (ModContainer mod : FabricLoader.getInstance().getAllMods()) { for (ModContainer mod : FabricLoader.getInstance().getAllMods()) {

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.14.21", "fabricloader": ">=0.15.11",
"fabric": "*", "fabric": "*",
"minecraft": ">=1.20", "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

@ -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,20 +23,22 @@
* @link https://github.com/GeyserMC/Geyser * @link https://github.com/GeyserMC/Geyser
*/ */
package org.geysermc.geyser.platform.sponge; package org.geysermc.geyser.platform.neoforge;
import org.geysermc.geyser.GeyserMain; import org.geysermc.geyser.GeyserMain;
public class GeyserSpongeMain extends GeyserMain { public class GeyserNeoForgeMain extends GeyserMain {
public static void main(String[] args) { public static void main(String[] args) {
new GeyserSpongeMain().displayMessage(); new GeyserNeoForgeMain().displayMessage();
} }
@Override
public String getPluginType() { public String getPluginType() {
return "Sponge"; return "NeoForge";
} }
@Override
public String getPluginFolder() { public String getPluginFolder() {
return "mods"; 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;
}
}
}

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