Merge branch 'master' into feature/skin-rework

This commit is contained in:
rtm516 2024-04-25 16:20:47 +01:00
commit 7c858cd180
No known key found for this signature in database
GPG Key ID: 331715B8B007C67A
101 changed files with 14360 additions and 413 deletions

View File

@ -1,43 +1,40 @@
name: Build Pull Request
name: Build Remote
on:
pull_request:
merge_group:
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 up JDK 17
- name: Set Build Number
run: |
echo "BUILD_NUMBER=${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
- name: Set up JDK 21
# See https://github.com/actions/setup-java/commits
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
with:
java-version: 17
java-version: 21
distribution: temurin
- name: Check if the author has forked the API repo
# See https://github.com/Kas-tle/find-forks-action/commits
uses: Kas-tle/find-forks-action@1b5447d1e3c7a8ed79583dd817cc5399686eed3a
id: find_forks
with:
owner: GeyserMC
repo: api
token: ${{ secrets.GITHUB_TOKEN }}
- name: Use author's API repo if it exists
if: ${{ steps.find_forks.outputs.target_branch_found == 'true' }}
env:
API_FORK_URL: ${{ steps.find_forks.outputs.user_fork_url }}
API_FORK_BRANCH: ${{ github.event.pull_request.head.ref }}
run: |
git clone "${API_FORK_URL}" --single-branch --branch "${API_FORK_BRANCH}" api
cd api
./gradlew publishToMavenLocal
- name: Checkout repository and submodules
# See https://github.com/actions/checkout/commits
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
repository: ${{ inputs.repository }}
ref: ${{ inputs.ref }}
submodules: recursive
path: geyser
@ -46,11 +43,12 @@ jobs:
uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2 # v2.1.1
- name: Build Geyser
# See https://github.com/gradle/gradle-build-action/commits
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 from https://github.com/gradle/actions/commits
# See https://github.com/gradle/actions/commits
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
with:
arguments: build
build-root-directory: geyser
cache-read-only: true
- name: Archive artifacts (Geyser Fabric)
# See https://github.com/actions/upload-artifact/commits
@ -101,4 +99,4 @@ jobs:
with:
name: Geyser ViaProxy
path: geyser/bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar
if-no-files-found: error
if-no-files-found: error

View File

@ -7,7 +7,9 @@ on:
- 'gh-readonly-queue/**'
paths-ignore:
- '.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'
- '.gitignore'
- 'CONTRIBUTING.md'
@ -19,7 +21,16 @@ on:
jobs:
build:
runs-on: ubuntu-latest
env:
PROJECT: 'geyser'
steps:
- name: Set Build Number
env:
BUILD_JSON: ${{ vars.RELEASEACTION_PREVRELEASE }}
run: |
BUILD_NUMBER=$(echo $BUILD_JSON | jq --arg branch "${GITHUB_REF_NAME}" 'if .[$branch] == null then 1 else .[$branch] | .t | tonumber + 1 end // 1')
echo "BUILD_NUMBER=${BUILD_NUMBER:=$GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
- name: Checkout repository and submodules
# See https://github.com/actions/checkout/commits
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
@ -33,12 +44,12 @@ jobs:
# See https://github.com/actions/setup-java/commits
- uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
with:
java-version: 17
java-version: 21
distribution: temurin
- name: Build
# See https://github.com/gradle/gradle-build-action/commits
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 from https://github.com/gradle/actions/commits
# See https://github.com/gradle/actions/commits
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
with:
arguments: build
gradle-home-cache-cleanup: true
@ -103,6 +114,36 @@ jobs:
with:
arguments: publish
- name: Get Release Metadata
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
# See https://github.com/Kas-tle/base-release-action/releases/tag/main-11
uses: Kas-tle/base-release-action@b863fa0f89bd15267a96a72efb84aec25f168d4c # main-11
with:
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
- name: Update Generated Metadata
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
run: |
cat metadata.json
echo
version=$(cat gradle.properties | grep -o "version=[0-9\\.]*" | cut -d"=" -f2)
cat metadata.json | jq --arg project "${PROJECT}" --arg version "${version}" '
.
| .changes |= map({"commit", "summary", "message"})
| .downloads |= map_values({"name", "sha256"})
| {$project, "repo", $version, "number": .build, "changes", "downloads"}
' | tee metadata.json
echo
- name: Publish to Downloads API
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
shell: bash
@ -114,19 +155,13 @@ jobs:
# Save the private key to a file
echo "$DOWNLOADS_PRIVATE_KEY" > id_ecdsa
chmod 600 id_ecdsa
# Set the project
project=geyser
# Get the version from gradle.properties
version=$(cat gradle.properties | grep -o "version=[0-9\\.]*" | cut -d"=" -f2)
# Create the build folder
ssh -o StrictHostKeyChecking=no -i id_ecdsa $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP mkdir -p "~/uploads/$project/$GITHUB_RUN_NUMBER/"
ssh -o StrictHostKeyChecking=no -i id_ecdsa $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP mkdir -p "~/uploads/$PROJECT/$GITHUB_RUN_NUMBER/"
# Copy over artifacts
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" bootstrap/**/build/libs/Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$project/$GITHUB_RUN_NUMBER/
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" bootstrap/mod/**/build/libs/Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$project/$GITHUB_RUN_NUMBER/
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" bootstrap/**/build/libs/Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$GITHUB_RUN_NUMBER/
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" bootstrap/mod/**/build/libs/Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$GITHUB_RUN_NUMBER/
# Run the build script
# 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/
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 (Fabric)
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5

94
.github/workflows/preview.yml vendored Normal file
View File

@ -0,0 +1,94 @@
name: Upload Preview
on:
workflow_dispatch:
inputs:
runId:
required: true
description: 'ID of the action to pull artifacts from'
build:
required: true
description: 'Build number for the release'
version:
required: true
description: 'Version under which to upload to the Downloads API'
workflow_call:
inputs:
build:
required: true
description: 'Build number for the release'
type: string
version:
required: true
description: 'Version under which to upload to the Downloads API'
type: string
jobs:
upload:
runs-on: ubuntu-latest
env:
PROJECT: 'geyserpreview'
steps:
- name: Set Variables
id: setvars
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "BUILD=${{ github.event.inputs.build }}" >> $GITHUB_ENV
echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
echo "RUN=${{ github.event.inputs.runId }}" >> $GITHUB_OUTPUT
else
echo "BUILD=${{ inputs.build }}" >> $GITHUB_ENV
echo "VERSION=${{ inputs.version }}" >> $GITHUB_ENV
echo "RUN=${{ github.run_id }}" >> $GITHUB_OUTPUT
fi
- uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427
with:
run-id: ${{ steps.setvars.outputs.RUN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
merge-multiple: true
- name: Get Preview Metadata
if: success()
# See https://github.com/Kas-tle/base-release-action/releases/tag/main-11
uses: Kas-tle/base-release-action@664c39985eb9d0d393ce98e7eb8414d3d98e762a # main-11
with:
appID: ${{ secrets.RELEASE_APP_ID }}
appPrivateKey: ${{ secrets.RELEASE_APP_PK }}
files: |
bungeecord:Geyser-BungeeCord.jar
fabric:Geyser-Fabric.jar
neoforge:Geyser-NeoForge.jar
spigot:Geyser-Spigot.jar
standalone:Geyser-Standalone.jar
velocity:Geyser-Velocity.jar
viaproxy:Geyser-ViaProxy.jar
releaseEnabled: false
saveMetadata: true
updateReleaseData: false
- name: Update Generated Metadata
if: success()
run: |
cat metadata.json
echo
cat metadata.json | jq --arg project "${PROJECT}" --arg version "${VERSION}" --arg number "${BUILD}" '
.
| .downloads |= map_values({"name", "sha256"})
| {$project, "repo", $version, "number": $number | tonumber, "changes": [], "downloads"}
' | tee metadata.json
- name: Publish to Downloads API
if: success()
shell: bash
env:
DOWNLOADS_USERNAME: ${{ vars.DOWNLOADS_USERNAME }}
DOWNLOADS_PRIVATE_KEY: ${{ secrets.DOWNLOADS_PRIVATE_KEY }}
DOWNLOADS_SERVER_IP: ${{ secrets.DOWNLOADS_SERVER_IP }}
run: |
# Save the private key to a file
echo "$DOWNLOADS_PRIVATE_KEY" > id_ecdsa
chmod 600 id_ecdsa
# Create the build folder
ssh -o StrictHostKeyChecking=no -i id_ecdsa $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP mkdir -p "~/uploads/$PROJECT/$BUILD/"
# Copy over artifacts
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$BUILD/
# Run the build script
# Push the metadata
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" metadata.json $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$BUILD/

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

@ -0,0 +1,24 @@
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: ./.github/workflows/build-remote.yml
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: ./.github/workflows/preview.yml
with:
build: ${{ github.run_number }}
version: pr.${{ github.event.pull_request.number }}

View File

@ -2,7 +2,7 @@
[![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)
[![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.
@ -14,7 +14,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
### Currently supporting Minecraft Bedrock 1.20.40 - 1.20.71 and Minecraft Java 1.20.4
### Currently supporting Minecraft Bedrock 1.20.40 - 1.20.80 and Minecraft Java 1.20.4
## Setting Up
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
- Near-perfect movement (to the point where anticheat on large servers is unlikely to ban you)
- Some Entity Flags
- Structure block UI
## 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.

View File

@ -0,0 +1,88 @@
/*
* 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
*/
@NonNull
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
*/
@Nullable
public InetSocketAddress getProxyIp() {
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

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -64,6 +64,14 @@ public interface NonVanillaCustomItemData extends CustomItemData {
*/
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.
*
@ -153,6 +161,13 @@ public interface NonVanillaCustomItemData extends CustomItemData {
return displayHandheld();
}
/**
* Gets the block the item places.
*
* @return the block the item places
*/
String block();
static NonVanillaCustomItemData.Builder builder() {
return GeyserApi.api().provider(NonVanillaCustomItemData.Builder.class);
}
@ -169,6 +184,8 @@ public interface NonVanillaCustomItemData extends CustomItemData {
Builder maxDamage(int maxDamage);
Builder attackDamage(int attackDamage);
Builder toolType(@Nullable String toolType);
Builder toolTier(@Nullable String toolTier);
@ -191,6 +208,8 @@ public interface NonVanillaCustomItemData extends CustomItemData {
Builder chargeable(boolean isChargeable);
Builder block(String block);
/**
* @deprecated Use {@link #displayHandheld(boolean)} instead.
*/

View File

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

View File

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

View File

@ -7,6 +7,8 @@ architectury {
fabric()
}
val includeTransitive: Configuration = configurations.getByName("includeTransitive")
dependencies {
modImplementation(libs.fabric.loader)
modApi(libs.fabric.api)
@ -15,14 +17,28 @@ dependencies {
shadow(project(path = ":mod", configuration = "transformProductionFabric")) {
isTransitive = false
}
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")
}
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)
}
@ -31,6 +47,12 @@ application {
mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain")
}
relocate("org.cloudburstmc.netty")
relocate("org.cloudburstmc.protocol")
relocate("com.github.steveice10.mc.protocol")
relocate("com.github.steveice10.mc.auth")
relocate("com.github.steveice10.packetlib")
tasks {
remapJar {
archiveBaseName.set("Geyser-Fabric")

View File

@ -2,11 +2,17 @@ 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 {
@ -21,12 +27,14 @@ dependencies {
shadow(project(path = ":mod", configuration = "transformProductionNeoForge")) {
isTransitive = false
}
shadow(projects.core) {
exclude(group = "com.google.guava", module = "guava")
exclude(group = "com.google.code.gson", module = "gson")
exclude(group = "org.slf4j")
exclude(group = "io.netty.incubator")
}
shadow(projects.core) { isTransitive = false }
// Let's shade in our own api
shadow(projects.api) { isTransitive = false }
shadow(projects.common) { isTransitive = false }
// Include all transitive deps of core via JiJ
includeTransitive(projects.core)
}
application {
@ -34,10 +42,6 @@ application {
}
tasks {
shadowJar {
relocate("it.unimi.dsi.fastutil", "org.geysermc.relocate.fastutil")
}
remapJar {
archiveBaseName.set("Geyser-NeoForge")
}

View File

@ -11,18 +11,11 @@ dependencies {
implementation(libs.commodore)
implementation(libs.adventure.text.serializer.bungeecord)
// Both folia-api and paper-mojangapi only provide Java 17 versions for 1.19
compileOnly(libs.folia.api) {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
}
}
compileOnly(libs.paper.mojangapi) {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
}
}
compileOnly(libs.folia.api)
compileOnly(libs.paper.mojangapi)
compileOnlyApi(libs.viaversion)
}
platformRelocate("it.unimi.dsi.fastutil")
@ -48,6 +41,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
// We cannot shade Netty, or else native libraries will not load
// Needed because older Spigot builds do not provide the haproxy module
exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("io.netty:netty-transport-classes-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))

View File

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

View File

@ -1,5 +1,7 @@
dependencies {
api(projects.core)
compileOnlyApi(libs.viaproxy)
}
platformRelocate("net.kyori")
@ -20,6 +22,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
dependencies {
exclude(dependency("com.google.*:.*"))
exclude(dependency("io.netty:.*"))
exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("org.slf4j:.*"))
exclude(dependency("org.ow2.asm:.*"))
}

View File

@ -58,19 +58,20 @@ fun Project.platformRelocate(pattern: String, exclusion: String = "") {
val providedDependencies = mutableMapOf<String, MutableSet<String>>()
fun Project.provided(pattern: String, name: String, version: String, excludedOn: Int = 0b110) {
fun getProvidedDependenciesForProject(projectName: String): MutableSet<String> {
return providedDependencies.getOrDefault(projectName, emptySet()).toMutableSet()
}
fun Project.provided(pattern: String, name: String, excludedOn: Int = 0b110) {
providedDependencies.getOrPut(project.name) { mutableSetOf() }
.add("${calcExclusion(pattern, 0b100, excludedOn)}:" +
"${calcExclusion(name, 0b10, excludedOn)}:" +
calcExclusion(version, 0b1, excludedOn))
dependencies.add("compileOnlyApi", "$pattern:$name:$version")
.add("${calcExclusion(pattern, 0b100, excludedOn)}:${calcExclusion(name, 0b10, excludedOn)}")
}
fun Project.provided(dependency: ProjectDependency) =
provided(dependency.group!!, dependency.name, dependency.version!!)
provided(dependency.group!!, dependency.name)
fun Project.provided(dependency: MinimalExternalModuleDependency) =
provided(dependency.module.group, dependency.module.name, dependency.versionConstraint.requiredVersion)
provided(dependency.module.group, dependency.module.name)
fun Project.provided(provider: Provider<MinimalExternalModuleDependency>) =
provided(provider.get())

View File

@ -11,6 +11,33 @@ plugins {
id("com.modrinth.minotaur")
}
// These are provided by Minecraft/modded platforms already, no need to include them
provided("com.google.code.gson", "gson")
provided("com.google.guava", ".*")
provided("org.slf4j", "slf4j-api")
provided("com.nukkitx.fastutil", ".*")
provided("org.cloudburstmc.fastutil.maps", ".*")
provided("org.cloudburstmc.fastutil.sets", ".*")
provided("org.cloudburstmc.fastutil.commons", ".*")
provided("org.cloudburstmc.fastutil", ".*")
provided("org.checkerframework", "checker-qual")
provided("io.netty", "netty-transport-classes-epoll")
provided("io.netty", "netty-transport-native-epoll")
provided("io.netty", "netty-transport-native-unix-common")
provided("io.netty", "netty-transport-classes-kqueue")
provided("io.netty", "netty-transport-native-kqueue")
provided("io.netty.incubator", "netty-incubator-transport-native-io_uring")
provided("io.netty.incubator", "netty-incubator-transport-classes-io_uring")
provided("io.netty", "netty-handler")
provided("io.netty", "netty-common")
provided("io.netty", "netty-buffer")
provided("io.netty", "netty-resolver")
provided("io.netty", "netty-transport")
provided("io.netty", "netty-codec")
provided("io.netty", "netty-resolver-dns")
provided("io.netty", "netty-resolver-dns-native-macos")
provided("org.ow2.asm", "asm")
architectury {
minecraft = "1.20.4"
}
@ -19,6 +46,10 @@ loom {
silentMojangMappingsLicense()
}
configurations {
create("includeTransitive").isTransitive = true
}
tasks {
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
@ -34,28 +65,6 @@ tasks {
// The remapped shadowJar is the final desired mod 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 {
@ -73,12 +82,34 @@ tasks {
}
}
afterEvaluate {
val providedDependencies = getProvidedDependenciesForProject(project.name)
// These are shaded, no need to JiJ them
configurations["shadow"].dependencies.forEach {shadowed ->
println("Not including shadowed dependency: ${shadowed.group}:${shadowed.name}")
providedDependencies.add("${shadowed.group}:${shadowed.name}")
}
// Now: Include all transitive dependencies that aren't excluded
configurations["includeTransitive"].resolvedConfiguration.resolvedArtifacts.forEach { dep ->
if (!providedDependencies.contains("${dep.moduleVersion.id.group}:${dep.moduleVersion.id.name}")
and !providedDependencies.contains("${dep.moduleVersion.id.group}:.*")) {
println("Including dependency via JiJ: ${dep.id}")
dependencies.add("include", dep.moduleVersion.id.toString())
} else {
println("Not including ${dep.id} for ${project.name}!")
}
}
}
dependencies {
minecraft("com.mojang:minecraft:1.20.4")
mappings(loom.officialMojangMappings())
}
repositories {
// mavenLocal()
maven("https://repo.opencollab.dev/maven-releases/")
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://jitpack.io")

View File

@ -7,3 +7,9 @@ indra {
publishSnapshotsTo("geysermc", "https://repo.opencollab.dev/maven-snapshots")
publishReleasesTo("geysermc", "https://repo.opencollab.dev/maven-releases")
}
publishing {
// skip shadow jar from publishing. Workaround for https://github.com/johnrengelman/shadow/issues/651
val javaComponent = project.components["java"] as AdhocComponentWithVariants
javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) { skip() }
}

View File

@ -7,7 +7,6 @@ plugins {
tasks {
named<Jar>("jar") {
archiveClassifier.set("unshaded")
from(project.rootProject.file("LICENSE"))
}
val shadowJar = named<ShadowJar>("shadowJar") {

View File

@ -6,6 +6,10 @@ plugins {
}
dependencies {
constraints {
implementation(libs.raknet) // Ensure protocol does not override the RakNet version
}
api(projects.common)
api(projects.api)
@ -43,6 +47,8 @@ dependencies {
implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-x86_64" } }
implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-aarch_64" } }
implementation(libs.netty.transport.native.kqueue) { artifact { classifier = "osx-x86_64" } }
implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-x86_64" } }
implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-aarch_64" } }
// Adventure text serialization
api(libs.bundles.adventure)
@ -62,11 +68,6 @@ dependencies {
api(libs.events)
}
configurations.api {
// This is still experimental - additionally, it could only really benefit standalone
exclude(group = "io.netty.incubator", module = "netty-incubator-transport-native-io_uring")
}
tasks.processResources {
// This is solely for backwards compatibility for other programs that used this file before the switch to gradle.
// It used to be generated by the maven Git-Commit-Id-Plugin
@ -97,7 +98,7 @@ configure<BlossomExtension> {
}
fun Project.buildNumber(): Int =
(System.getenv("GITHUB_RUN_NUMBER") ?: jenkinsBuildNumber())?.let { Integer.parseInt(it) } ?: -1
(System.getenv("BUILD_NUMBER"))?.let { Integer.parseInt(it) } ?: -1
inner class GitInfo {
val branch: String
@ -130,9 +131,6 @@ inner class GitInfo {
}
}
// todo remove this when we're not using Jenkins anymore
fun jenkinsBuildNumber(): String? = System.getenv("BUILD_NUMBER")
// Manual task to download the bedrock data files from the CloudburstMC/Data repository
// Invoke with ./gradlew :core:downloadBedrockData --suffix=1_20_70
// Set suffix to the current Bedrock version

View File

@ -40,9 +40,11 @@ import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.network.CIDRMatcher;
import org.geysermc.geyser.text.AsteriskSerializer;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.WebUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@ -233,7 +235,18 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
List<CIDRMatcher> matchers = this.whitelistedIPsMatchers;
if (matchers == null) {
synchronized (this) {
this.whitelistedIPsMatchers = matchers = proxyProtocolWhitelistedIPs.stream()
// Check if proxyProtocolWhitelistedIPs contains URLs we need to fetch and parse by line
List<String> whitelistedCIDRs = new ArrayList<>();
for (String ip: proxyProtocolWhitelistedIPs) {
if (!ip.startsWith("http")) {
whitelistedCIDRs.add(ip);
continue;
}
WebUtils.getLineStream(ip).forEach(whitelistedCIDRs::add);
}
this.whitelistedIPsMatchers = matchers = whitelistedCIDRs.stream()
.map(CIDRMatcher::new)
.collect(Collectors.toList());
}

View File

@ -78,6 +78,7 @@ public class DumpInfo {
private final GeyserConfiguration config;
private final Floodgate floodgate;
private final Object2IntMap<DeviceOs> userPlatforms;
private final int connectionAttempts;
private final HashInfo hashInfo;
private final RamInfo ramInfo;
private LogsInfo logsInfo;
@ -129,6 +130,8 @@ public class DumpInfo {
userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1);
}
this.connectionAttempts = GeyserImpl.getInstance().getGeyserServer().getConnectionAttempts();
this.bootstrapInfo = GeyserImpl.getInstance().getBootstrap().getDumpInfo();
this.flagsInfo = new FlagsInfo();

View File

@ -51,7 +51,7 @@ public enum GeyserAttributeType {
MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f),
// Bedrock Attributes
ABSORPTION(null, "minecraft:absorption", 0f, Float.MAX_VALUE, 0f),
ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f),
EXHAUSTION(null, "minecraft:player.exhaustion", 0f, 5f, 0f),
EXPERIENCE(null, "minecraft:player.experience", 0f, 1f, 0f),
EXPERIENCE_LEVEL(null, "minecraft:player.level", 0f, 24791.00f, 0f),
@ -66,6 +66,10 @@ public enum GeyserAttributeType {
private final float maximum;
private final float defaultValue;
public AttributeData getAttribute() {
return getAttribute(defaultValue);
}
public AttributeData getAttribute(float value) {
return getAttribute(value, maximum);
}

View File

@ -42,7 +42,7 @@ import org.geysermc.geyser.util.InteractiveTag;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BoatEntity extends Entity {
public class BoatEntity extends Entity implements Tickable {
/**
* Required when IS_BUOYANT is sent in order for boats to work in the water. <br>
@ -58,6 +58,7 @@ public class BoatEntity extends Entity {
private float paddleTimeLeft;
private boolean isPaddlingRight;
private float paddleTimeRight;
private boolean doTick;
/**
* Saved for using the "pick" functionality on a boat.
@ -133,34 +134,16 @@ public class BoatEntity extends Entity {
public void setPaddlingLeft(BooleanEntityMetadata entityMetadata) {
isPaddlingLeft = entityMetadata.getPrimitiveValue();
if (isPaddlingLeft) {
// Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing
// This is an asynchronous method that emulates Bedrock rowing until "false" is sent.
paddleTimeLeft = 0f;
if (!this.passengers.isEmpty()) {
// Get the entity by the first stored passenger and convey motion in this manner
Entity entity = this.passengers.get(0);
if (entity != null) {
updateLeftPaddle(session, entity);
}
}
} else {
// Indicate that the row position should be reset
if (!isPaddlingLeft) {
paddleTimeLeft = 0.0f;
dirtyMetadata.put(EntityDataTypes.ROW_TIME_LEFT, 0.0f);
}
}
public void setPaddlingRight(BooleanEntityMetadata entityMetadata) {
isPaddlingRight = entityMetadata.getPrimitiveValue();
if (isPaddlingRight) {
paddleTimeRight = 0f;
if (!this.passengers.isEmpty()) {
Entity entity = this.passengers.get(0);
if (entity != null) {
updateRightPaddle(session, entity);
}
}
} else {
if (!isPaddlingRight) {
paddleTimeRight = 0.0f;
dirtyMetadata.put(EntityDataTypes.ROW_TIME_RIGHT, 0.0f);
}
}
@ -186,29 +169,26 @@ public class BoatEntity extends Entity {
}
}
private void updateLeftPaddle(GeyserSession session, Entity rower) {
@Override
public void tick() {
// Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing
doTick = !doTick; // Run every 100 ms
if (!doTick || passengers.isEmpty()) {
return;
}
Entity rower = passengers.get(0);
if (rower == null) {
return;
}
if (isPaddlingLeft) {
paddleTimeLeft += ROWING_SPEED;
sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_LEFT, paddleTimeLeft);
session.scheduleInEventLoop(() ->
updateLeftPaddle(session, rower),
100,
TimeUnit.MILLISECONDS
);
}
}
private void updateRightPaddle(GeyserSession session, Entity rower) {
if (isPaddlingRight) {
paddleTimeRight += ROWING_SPEED;
sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_RIGHT, paddleTimeRight);
session.scheduleInEventLoop(() ->
updateRightPaddle(session, rower),
100,
TimeUnit.MILLISECONDS
);
}
}

View File

@ -200,11 +200,9 @@ public class Entity implements GeyserEntity {
/**
* Despawns the entity
*
* @return can be deleted
*/
public boolean despawnEntity() {
if (!valid) return true;
public void despawnEntity() {
if (!valid) return;
for (Entity passenger : passengers) { // Make sure all passengers on the despawned entity are updated
if (passenger == null) continue;
@ -218,7 +216,6 @@ public class Entity implements GeyserEntity {
session.sendUpstreamPacket(removeEntityPacket);
valid = false;
return true;
}
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) {

View File

@ -72,6 +72,9 @@ public class FireballEntity extends ThrowableEntity {
@Override
public void tick() {
if (removedInVoid()) {
return;
}
moveAbsoluteImmediate(tickMovement(position), getYaw(), getPitch(), getHeadYaw(), false, false);
}
}

View File

@ -133,6 +133,9 @@ public class FishingHookEntity extends ThrowableEntity {
@Override
public void tick() {
if (removedInVoid()) {
return;
}
if (hooked || !isInAir() && !isInWater() || isOnGround()) {
motion = Vector3f.ZERO;
return;

View File

@ -74,7 +74,7 @@ public class ItemEntity extends ThrowableEntity {
@Override
public void tick() {
if (isInWater()) {
if (removedInVoid() || isInWater()) {
return;
}
if (!isOnGround() || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) {

View File

@ -148,7 +148,7 @@ public class ItemFrameEntity extends Entity {
}
@Override
public boolean despawnEntity() {
public void despawnEntity() {
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(bedrockPosition);
@ -161,7 +161,6 @@ public class ItemFrameEntity extends Entity {
session.getItemFrameCache().remove(bedrockPosition, this);
valid = false;
return true;
}
private NbtMap getDefaultTag() {

View File

@ -55,6 +55,9 @@ public class ThrowableEntity extends Entity implements Tickable {
*/
@Override
public void tick() {
if (removedInVoid()) {
return;
}
moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false);
float drag = getDrag();
float gravity = getGravity();
@ -170,14 +173,14 @@ public class ThrowableEntity extends Entity implements Tickable {
}
@Override
public boolean despawnEntity() {
public void despawnEntity() {
if (definition.entityType() == EntityType.ENDER_PEARL) {
LevelEventPacket particlePacket = new LevelEventPacket();
particlePacket.setType(LevelEvent.PARTICLE_TELEPORT);
particlePacket.setPosition(position);
session.sendUpstreamPacket(particlePacket);
}
return super.despawnEntity();
super.despawnEntity();
}
@Override
@ -191,4 +194,17 @@ public class ThrowableEntity extends Entity implements Tickable {
moveAbsoluteImmediate(position, yaw, pitch, headYaw, isOnGround, teleported);
lastJavaPosition = position;
}
/**
* Removes the entity if it is 64 blocks below the world.
*
* @return true if the entity was removed
*/
public boolean removedInVoid() {
if (position.getY() < session.getDimensionType().minY() - 64) {
session.getEntityCache().removeEntity(this);
return true;
}
return false;
}
}

View File

@ -99,11 +99,11 @@ public class ArmorStandEntity extends LivingEntity {
}
@Override
public boolean despawnEntity() {
public void despawnEntity() {
if (secondEntity != null) {
secondEntity.despawnEntity();
}
return super.despawnEntity();
super.despawnEntity();
}
@Override

View File

@ -45,7 +45,7 @@ import java.util.UUID;
public class CatEntity extends TameableEntity {
private byte collarColor;
private byte collarColor = 14; // Red - default
public CatEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
@ -76,10 +76,7 @@ public class CatEntity extends TameableEntity {
@Override
public void setTameableFlags(ByteEntityMetadata entityMetadata) {
super.setTameableFlags(entityMetadata);
// Update collar color if tamed
if (getFlag(EntityFlag.TAMED)) {
dirtyMetadata.put(EntityDataTypes.COLOR, collarColor);
}
updateCollarColor();
}
public void setCatVariant(IntEntityMetadata entityMetadata) {
@ -101,6 +98,10 @@ public class CatEntity extends TameableEntity {
public void setCollarColor(IntEntityMetadata entityMetadata) {
collarColor = (byte) entityMetadata.getPrimitiveValue();
updateCollarColor();
}
private void updateCollarColor() {
// Needed or else wild cats are a red color
if (getFlag(EntityFlag.TAMED)) {
dirtyMetadata.put(EntityDataTypes.COLOR, collarColor);

View File

@ -32,6 +32,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
@ -41,6 +42,7 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
@ -54,7 +56,7 @@ public class WolfEntity extends TameableEntity {
Items.PORKCHOP, Items.BEEF, Items.RABBIT, Items.COOKED_PORKCHOP, Items.COOKED_BEEF, Items.ROTTEN_FLESH, Items.MUTTON, Items.COOKED_MUTTON,
Items.COOKED_RABBIT);
private byte collarColor;
private byte collarColor = 14; // Red - default
public WolfEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
@ -64,19 +66,27 @@ public class WolfEntity extends TameableEntity {
public void setTameableFlags(ByteEntityMetadata entityMetadata) {
super.setTameableFlags(entityMetadata);
// Reset wolf color
byte xd = entityMetadata.getPrimitiveValue();
boolean angry = (xd & 0x02) == 0x02;
if (angry) {
if (getFlag(EntityFlag.ANGRY)) {
dirtyMetadata.put(EntityDataTypes.COLOR, (byte) 0);
} else if (getFlag(EntityFlag.TAMED)) {
updateCollarColor();
// This fixes tail angle when taming
UpdateAttributesPacket packet = new UpdateAttributesPacket();
packet.setRuntimeEntityId(geyserId);
packet.setAttributes(Collections.singletonList(createHealthAttribute()));
session.sendUpstreamPacket(packet);
}
}
public void setCollarColor(IntEntityMetadata entityMetadata) {
collarColor = (byte) entityMetadata.getPrimitiveValue();
if (getFlag(EntityFlag.ANGRY)) {
return;
if (!getFlag(EntityFlag.ANGRY) && getFlag(EntityFlag.TAMED)) {
updateCollarColor();
}
}
private void updateCollarColor() {
dirtyMetadata.put(EntityDataTypes.COLOR, collarColor);
if (ownerBedrockId == 0) {
// If a color is set and there is no owner entity ID, set one.

View File

@ -148,11 +148,11 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
}
@Override
public boolean despawnEntity() {
public void despawnEntity() {
for (EnderDragonPartEntity part : allParts) {
part.despawnEntity();
}
return super.despawnEntity();
super.despawnEntity();
}
@Override

View File

@ -25,6 +25,11 @@
package org.geysermc.geyser.entity.type.player;
import com.github.steveice10.mc.protocol.codec.NbtComponentSerializer;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.BlankFormat;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.FixedFormat;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.NumberFormat;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.StyledFormat;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
@ -33,6 +38,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEnt
import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
@ -55,6 +61,7 @@ import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.entity.type.living.animal.tameable.ParrotEntity;
@ -63,6 +70,7 @@ import org.geysermc.geyser.scoreboard.Score;
import org.geysermc.geyser.scoreboard.Team;
import org.geysermc.geyser.scoreboard.UpdateType;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.ChunkUtils;
@ -283,7 +291,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
attributesPacket.setRuntimeEntityId(geyserId);
// Setting to a higher maximum since plugins/datapacks can probably extend the Bedrock soft limit
attributesPacket.setAttributes(Collections.singletonList(
new AttributeData("minecraft:absorption", 0.0f, 1024f, entityMetadata.getPrimitiveValue(), 0.0f)));
GeyserAttributeType.ABSORPTION.getAttribute(entityMetadata.getPrimitiveValue())));
session.sendUpstreamPacket(attributesPacket);
}
@ -307,7 +315,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
* Sets the parrot occupying the shoulder. Bedrock Edition requires a full entity whereas Java Edition just
* spawns it from the NBT data provided
*/
private void setParrot(CompoundTag tag, boolean isLeft) {
protected void setParrot(CompoundTag tag, boolean isLeft) {
if (tag != null && !tag.isEmpty()) {
if ((isLeft && leftParrot != null) || (!isLeft && rightParrot != null)) {
// No need to update a parrot's data when it already exists
@ -414,14 +422,36 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
public void setBelowNameText(Objective objective) {
if (objective != null && objective.getUpdateType() != UpdateType.REMOVE) {
int amount;
Score score = objective.getScores().get(username);
String numberString;
NumberFormat numberFormat;
int amount;
if (score != null) {
amount = score.getCurrentData().getScore();
amount = score.getScore();
numberFormat = score.getNumberFormat();
if (numberFormat == null) {
numberFormat = objective.getNumberFormat();
}
} else {
amount = 0;
numberFormat = objective.getNumberFormat();
}
String displayString = amount + " " + objective.getDisplayName();
if (numberFormat instanceof BlankFormat) {
numberString = "";
} else if (numberFormat instanceof FixedFormat fixedFormat) {
numberString = MessageTranslator.convertMessage(fixedFormat.getValue());
} else if (numberFormat instanceof StyledFormat styledFormat) {
CompoundTag styledAmount = styledFormat.getStyle().clone();
styledAmount.put(new StringTag("text", String.valueOf(amount)));
numberString = MessageTranslator.convertJsonMessage(
NbtComponentSerializer.tagComponentToJson(styledAmount).toString());
} else {
numberString = String.valueOf(amount);
}
String displayString = numberString + " " + ChatColor.RESET + objective.getDisplayName();
if (valid) {
// Already spawned - we still need to run the rest of this code because the spawn packet will be
@ -430,13 +460,22 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
packet.setRuntimeEntityId(geyserId);
packet.getMetadata().put(EntityDataTypes.SCORE, displayString);
session.sendUpstreamPacket(packet);
} else {
// Not spawned yet, store score value in dirtyMetadata to be picked up by #spawnEntity
dirtyMetadata.put(EntityDataTypes.SCORE, displayString);
}
} else {
if (valid) {
SetEntityDataPacket packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(geyserId);
packet.getMetadata().put(EntityDataTypes.SCORE, "");
session.sendUpstreamPacket(packet);
} else {
// Not spawned yet, store score value in dirtyMetadata to be picked up by #spawnEntity
dirtyMetadata.put(EntityDataTypes.SCORE, "");
}
} else if (valid) {
SetEntityDataPacket packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(geyserId);
packet.getMetadata().put(EntityDataTypes.SCORE, "");
session.sendUpstreamPacket(packet);
}
}
/**

View File

@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeTyp
import com.github.steveice10.mc.protocol.data.game.entity.metadata.GlobalPos;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
@ -255,13 +256,51 @@ public class SessionPlayerEntity extends PlayerEntity {
return session.getAuthData().uuid();
}
@Override
public void setAbsorptionHearts(FloatEntityMetadata entityMetadata) {
// The bedrock client can glitch when sending a health and absorption attribute in the same tick
// This can happen when switching servers. Resending the absorption attribute fixes the issue
attributes.put(GeyserAttributeType.ABSORPTION, GeyserAttributeType.ABSORPTION.getAttribute(entityMetadata.getPrimitiveValue()));
super.setAbsorptionHearts(entityMetadata);
}
public void resetMetadata() {
// Reset all metadata to their default values
// This is used when a player respawns
this.flags.clear();
this.initializeMetadata();
// Reset air
this.resetAir();
// Explicitly reset all metadata not handled by initializeMetadata
setParrot(null, true);
setParrot(null, false);
// Absorption is metadata in java edition
attributes.remove(GeyserAttributeType.ABSORPTION);
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
attributesPacket.setRuntimeEntityId(geyserId);
attributesPacket.setAttributes(Collections.singletonList(
GeyserAttributeType.ABSORPTION.getAttribute(0f)));
session.sendUpstreamPacket(attributesPacket);
dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, 0);
dirtyMetadata.put(EntityDataTypes.EFFECT_AMBIENCE, (byte) 0);
dirtyMetadata.put(EntityDataTypes.FREEZING_EFFECT_STRENGTH, 0f);
silent = false;
}
public void resetAttributes() {
attributes.clear();
maxHealth = GeyserAttributeType.MAX_HEALTH.getDefaultValue();
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
attributesPacket.setRuntimeEntityId(geyserId);
attributesPacket.setAttributes(Collections.singletonList(
GeyserAttributeType.MOVEMENT_SPEED.getAttribute()));
session.sendUpstreamPacket(attributesPacket);
}
public void resetAir() {

View File

@ -31,6 +31,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.extension.ExtensionDescription;
import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException;
import org.geysermc.geyser.text.GeyserLocale;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor;
@ -48,7 +49,7 @@ public record GeyserExtensionDescription(@NonNull String id,
@NonNull String version,
@NonNull List<String> authors) implements ExtensionDescription {
private static final Yaml YAML = new Yaml(new CustomClassLoaderConstructor(Source.class.getClassLoader()));
private static final Yaml YAML = new Yaml(new CustomClassLoaderConstructor(Source.class.getClassLoader(), new LoaderOptions()));
public static final Pattern ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{0,63}");
public static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z_.-]+$");

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
* of this software and associated documentation files (the "Software"), to deal
@ -42,6 +42,7 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
private final int javaId;
private final int stackSize;
private final int maxDamage;
private final int attackDamage;
private final String toolType;
private final String toolTier;
private final String armorType;
@ -54,6 +55,7 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
private final boolean isEdible;
private final boolean canAlwaysEat;
private final boolean isChargeable;
private final String block;
public GeyserNonVanillaCustomItemData(Builder builder) {
super(builder.name, builder.customItemOptions, builder.displayName, builder.icon, builder.allowOffhand,
@ -64,6 +66,7 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
this.javaId = builder.javaId;
this.stackSize = builder.stackSize;
this.maxDamage = builder.maxDamage;
this.attackDamage = builder.attackDamage;
this.toolType = builder.toolType;
this.toolTier = builder.toolTier;
this.armorType = builder.armorType;
@ -76,6 +79,7 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
this.isEdible = builder.edible;
this.canAlwaysEat = builder.canAlwaysEat;
this.isChargeable = builder.chargeable;
this.block = builder.block;
}
@Override
@ -98,6 +102,11 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
return maxDamage;
}
@Override
public int attackDamage() {
return attackDamage;
}
@Override
public String toolType() {
return toolType;
@ -153,6 +162,11 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
return isChargeable;
}
@Override
public String block() {
return block;
}
public static class Builder extends GeyserCustomItemData.Builder implements NonVanillaCustomItemData.Builder {
private String identifier = null;
private int javaId = -1;
@ -161,6 +175,8 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
private int maxDamage = 0;
private int attackDamage = 0;
private String toolType = null;
private String toolTier = null;
@ -177,6 +193,7 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
private boolean edible = false;
private boolean canAlwaysEat = false;
private boolean chargeable = false;
private String block = null;
@Override
public Builder name(@NonNull String name) {
@ -248,6 +265,12 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
return this;
}
@Override
public NonVanillaCustomItemData.Builder attackDamage(int attackDamage) {
this.attackDamage = attackDamage;
return this;
}
@Override
public Builder toolType(@Nullable String toolType) {
this.toolType = toolType;
@ -324,6 +347,12 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
return this;
}
@Override
public Builder block(String block) {
this.block = block;
return this;
}
@Override
public NonVanillaCustomItemData build() {
if (identifier == null || javaId == -1) {

View File

@ -852,36 +852,36 @@ public final class Items {
public static final Item GOLD_INGOT = register(new Item("gold_ingot", builder()));
public static final Item NETHERITE_INGOT = register(new Item("netherite_ingot", builder()));
public static final Item NETHERITE_SCRAP = register(new Item("netherite_scrap", builder()));
public static final Item WOODEN_SWORD = register(new TieredItem("wooden_sword", ToolTier.WOODEN, builder().stackSize(1).maxDamage(59)));
public static final Item WOODEN_SHOVEL = register(new TieredItem("wooden_shovel", ToolTier.WOODEN, builder().stackSize(1).maxDamage(59)));
public static final Item WOODEN_PICKAXE = register(new TieredItem("wooden_pickaxe", ToolTier.WOODEN, builder().stackSize(1).maxDamage(59)));
public static final Item WOODEN_AXE = register(new TieredItem("wooden_axe", ToolTier.WOODEN, builder().stackSize(1).maxDamage(59)));
public static final Item WOODEN_HOE = register(new TieredItem("wooden_hoe", ToolTier.WOODEN, builder().stackSize(1).maxDamage(59)));
public static final Item STONE_SWORD = register(new TieredItem("stone_sword", ToolTier.STONE, builder().stackSize(1).maxDamage(131)));
public static final Item STONE_SHOVEL = register(new TieredItem("stone_shovel", ToolTier.STONE, builder().stackSize(1).maxDamage(131)));
public static final Item STONE_PICKAXE = register(new TieredItem("stone_pickaxe", ToolTier.STONE, builder().stackSize(1).maxDamage(131)));
public static final Item STONE_AXE = register(new TieredItem("stone_axe", ToolTier.STONE, builder().stackSize(1).maxDamage(131)));
public static final Item STONE_HOE = register(new TieredItem("stone_hoe", ToolTier.STONE, builder().stackSize(1).maxDamage(131)));
public static final Item GOLDEN_SWORD = register(new TieredItem("golden_sword", ToolTier.GOLDEN, builder().stackSize(1).maxDamage(32)));
public static final Item GOLDEN_SHOVEL = register(new TieredItem("golden_shovel", ToolTier.GOLDEN, builder().stackSize(1).maxDamage(32)));
public static final Item GOLDEN_PICKAXE = register(new TieredItem("golden_pickaxe", ToolTier.GOLDEN, builder().stackSize(1).maxDamage(32)));
public static final Item GOLDEN_AXE = register(new TieredItem("golden_axe", ToolTier.GOLDEN, builder().stackSize(1).maxDamage(32)));
public static final Item GOLDEN_HOE = register(new TieredItem("golden_hoe", ToolTier.GOLDEN, builder().stackSize(1).maxDamage(32)));
public static final Item IRON_SWORD = register(new TieredItem("iron_sword", ToolTier.IRON, builder().stackSize(1).maxDamage(250)));
public static final Item IRON_SHOVEL = register(new TieredItem("iron_shovel", ToolTier.IRON, builder().stackSize(1).maxDamage(250)));
public static final Item IRON_PICKAXE = register(new TieredItem("iron_pickaxe", ToolTier.IRON, builder().stackSize(1).maxDamage(250)));
public static final Item IRON_AXE = register(new TieredItem("iron_axe", ToolTier.IRON, builder().stackSize(1).maxDamage(250)));
public static final Item IRON_HOE = register(new TieredItem("iron_hoe", ToolTier.IRON, builder().stackSize(1).maxDamage(250)));
public static final Item DIAMOND_SWORD = register(new TieredItem("diamond_sword", ToolTier.DIAMOND, builder().stackSize(1).maxDamage(1561)));
public static final Item DIAMOND_SHOVEL = register(new TieredItem("diamond_shovel", ToolTier.DIAMOND, builder().stackSize(1).maxDamage(1561)));
public static final Item DIAMOND_PICKAXE = register(new TieredItem("diamond_pickaxe", ToolTier.DIAMOND, builder().stackSize(1).maxDamage(1561)));
public static final Item DIAMOND_AXE = register(new TieredItem("diamond_axe", ToolTier.DIAMOND, builder().stackSize(1).maxDamage(1561)));
public static final Item DIAMOND_HOE = register(new TieredItem("diamond_hoe", ToolTier.DIAMOND, builder().stackSize(1).maxDamage(1561)));
public static final Item NETHERITE_SWORD = register(new TieredItem("netherite_sword", ToolTier.NETHERITE, builder().stackSize(1).maxDamage(2031)));
public static final Item NETHERITE_SHOVEL = register(new TieredItem("netherite_shovel", ToolTier.NETHERITE, builder().stackSize(1).maxDamage(2031)));
public static final Item NETHERITE_PICKAXE = register(new TieredItem("netherite_pickaxe", ToolTier.NETHERITE, builder().stackSize(1).maxDamage(2031)));
public static final Item NETHERITE_AXE = register(new TieredItem("netherite_axe", ToolTier.NETHERITE, builder().stackSize(1).maxDamage(2031)));
public static final Item NETHERITE_HOE = register(new TieredItem("netherite_hoe", ToolTier.NETHERITE, builder().stackSize(1).maxDamage(2031)));
public static final Item WOODEN_SWORD = register(new TieredItem("wooden_sword", ToolTier.WOODEN, builder().stackSize(1).attackDamage(4).maxDamage(59)));
public static final Item WOODEN_SHOVEL = register(new TieredItem("wooden_shovel", ToolTier.WOODEN, builder().stackSize(1).attackDamage(2.5).maxDamage(59)));
public static final Item WOODEN_PICKAXE = register(new TieredItem("wooden_pickaxe", ToolTier.WOODEN, builder().stackSize(1).attackDamage(2).maxDamage(59)));
public static final Item WOODEN_AXE = register(new TieredItem("wooden_axe", ToolTier.WOODEN, builder().stackSize(1).attackDamage(7).maxDamage(59)));
public static final Item WOODEN_HOE = register(new TieredItem("wooden_hoe", ToolTier.WOODEN, builder().stackSize(1).attackDamage(1).maxDamage(59)));
public static final Item STONE_SWORD = register(new TieredItem("stone_sword", ToolTier.STONE, builder().stackSize(1).attackDamage(5).maxDamage(131)));
public static final Item STONE_SHOVEL = register(new TieredItem("stone_shovel", ToolTier.STONE, builder().stackSize(1).attackDamage(3.5).maxDamage(131)));
public static final Item STONE_PICKAXE = register(new TieredItem("stone_pickaxe", ToolTier.STONE, builder().stackSize(1).attackDamage(3).maxDamage(131)));
public static final Item STONE_AXE = register(new TieredItem("stone_axe", ToolTier.STONE, builder().stackSize(1).attackDamage(9).maxDamage(131)));
public static final Item STONE_HOE = register(new TieredItem("stone_hoe", ToolTier.STONE, builder().stackSize(1).attackDamage(1).maxDamage(131)));
public static final Item GOLDEN_SWORD = register(new TieredItem("golden_sword", ToolTier.GOLDEN, builder().stackSize(1).attackDamage(4).maxDamage(32)));
public static final Item GOLDEN_SHOVEL = register(new TieredItem("golden_shovel", ToolTier.GOLDEN, builder().stackSize(1).attackDamage(2.5).maxDamage(32)));
public static final Item GOLDEN_PICKAXE = register(new TieredItem("golden_pickaxe", ToolTier.GOLDEN, builder().stackSize(1).attackDamage(2).maxDamage(32)));
public static final Item GOLDEN_AXE = register(new TieredItem("golden_axe", ToolTier.GOLDEN, builder().stackSize(1).attackDamage(7).maxDamage(32)));
public static final Item GOLDEN_HOE = register(new TieredItem("golden_hoe", ToolTier.GOLDEN, builder().stackSize(1).attackDamage(1).maxDamage(32)));
public static final Item IRON_SWORD = register(new TieredItem("iron_sword", ToolTier.IRON, builder().stackSize(1).attackDamage(6).maxDamage(250)));
public static final Item IRON_SHOVEL = register(new TieredItem("iron_shovel", ToolTier.IRON, builder().stackSize(1).attackDamage(4.5).maxDamage(250)));
public static final Item IRON_PICKAXE = register(new TieredItem("iron_pickaxe", ToolTier.IRON, builder().stackSize(1).attackDamage(4).maxDamage(250)));
public static final Item IRON_AXE = register(new TieredItem("iron_axe", ToolTier.IRON, builder().stackSize(1).attackDamage(9).maxDamage(250)));
public static final Item IRON_HOE = register(new TieredItem("iron_hoe", ToolTier.IRON, builder().stackSize(1).attackDamage(1).maxDamage(250)));
public static final Item DIAMOND_SWORD = register(new TieredItem("diamond_sword", ToolTier.DIAMOND, builder().stackSize(1).attackDamage(7).maxDamage(1561)));
public static final Item DIAMOND_SHOVEL = register(new TieredItem("diamond_shovel", ToolTier.DIAMOND, builder().stackSize(1).attackDamage(5.5).maxDamage(1561)));
public static final Item DIAMOND_PICKAXE = register(new TieredItem("diamond_pickaxe", ToolTier.DIAMOND, builder().stackSize(1).attackDamage(5).maxDamage(1561)));
public static final Item DIAMOND_AXE = register(new TieredItem("diamond_axe", ToolTier.DIAMOND, builder().stackSize(1).attackDamage(9).maxDamage(1561)));
public static final Item DIAMOND_HOE = register(new TieredItem("diamond_hoe", ToolTier.DIAMOND, builder().stackSize(1).attackDamage(1).maxDamage(1561)));
public static final Item NETHERITE_SWORD = register(new TieredItem("netherite_sword", ToolTier.NETHERITE, builder().stackSize(1).attackDamage(8).maxDamage(2031)));
public static final Item NETHERITE_SHOVEL = register(new TieredItem("netherite_shovel", ToolTier.NETHERITE, builder().stackSize(1).attackDamage(6.5).maxDamage(2031)));
public static final Item NETHERITE_PICKAXE = register(new TieredItem("netherite_pickaxe", ToolTier.NETHERITE, builder().stackSize(1).attackDamage(6).maxDamage(2031)));
public static final Item NETHERITE_AXE = register(new TieredItem("netherite_axe", ToolTier.NETHERITE, builder().stackSize(1).attackDamage(10).maxDamage(2031)));
public static final Item NETHERITE_HOE = register(new TieredItem("netherite_hoe", ToolTier.NETHERITE, builder().stackSize(1).attackDamage(1).maxDamage(2031)));
public static final Item STICK = register(new Item("stick", builder()));
public static final Item BOWL = register(new Item("bowl", builder()));
public static final Item MUSHROOM_STEW = register(new Item("mushroom_stew", builder().stackSize(1)));
@ -1216,7 +1216,7 @@ public final class Items {
public static final Item MUSIC_DISC_5 = register(new Item("music_disc_5", builder().stackSize(1)));
public static final Item MUSIC_DISC_PIGSTEP = register(new Item("music_disc_pigstep", builder().stackSize(1)));
public static final Item DISC_FRAGMENT_5 = register(new Item("disc_fragment_5", builder()));
public static final Item TRIDENT = register(new Item("trident", builder().stackSize(1).maxDamage(250)));
public static final Item TRIDENT = register(new Item("trident", builder().stackSize(1).attackDamage(9).maxDamage(250)));
public static final Item PHANTOM_MEMBRANE = register(new Item("phantom_membrane", builder()));
public static final Item NAUTILUS_SHELL = register(new Item("nautilus_shell", builder()));
public static final Item HEART_OF_THE_SEA = register(new Item("heart_of_the_sea", builder()));

View File

@ -53,10 +53,6 @@ public enum ToolTier {
this.repairIngredients = Suppliers.memoize(repairIngredients::get);
}
public int getSpeed() {
return speed;
}
public Set<Item> getRepairIngredients() {
return repairIngredients.get();
}

View File

@ -47,6 +47,14 @@ public class ArmorItem extends Item {
if (tag.get("Trim") instanceof CompoundTag trim) {
StringTag material = trim.remove("material");
StringTag pattern = trim.remove("pattern");
// discard custom trim patterns/materials to prevent visual glitches on bedrock
if (!material.getValue().startsWith("minecraft:")
|| !pattern.getValue().startsWith("minecraft:")) {
tag.remove("Trim");
return;
}
// bedrock has an uppercase first letter key, and the value is not namespaced
trim.put(new StringTag("Material", stripNamespace(material.getValue())));
trim.put(new StringTag("Pattern", stripNamespace(pattern.getValue())));

View File

@ -50,14 +50,14 @@ public class Item {
private final String javaIdentifier;
private int javaId = -1;
private final int stackSize;
private final String toolType;
private final int attackDamage;
private final int maxDamage;
public Item(String javaIdentifier, Builder builder) {
this.javaIdentifier = Identifier.formalize(javaIdentifier).intern();
this.stackSize = builder.stackSize;
this.toolType = builder.toolType;
this.maxDamage = builder.maxDamage;
this.attackDamage = builder.attackDamage;
}
public String javaIdentifier() {
@ -72,6 +72,10 @@ public class Item {
return maxDamage;
}
public int attackDamage() {
return attackDamage;
}
public int maxStackSize() {
return stackSize;
}
@ -279,16 +283,17 @@ public class Item {
public static final class Builder {
private int stackSize = 64;
private String toolType;
private int maxDamage;
private int attackDamage;
public Builder stackSize(int stackSize) {
this.stackSize = stackSize;
return this;
}
public Builder setToolType(String toolType) {
this.toolType = toolType;
public Builder attackDamage(double attackDamage) {
// TODO properly store/send a double value once Bedrock supports it.. pls
this.attackDamage = (int) attackDamage;
return this;
}

View File

@ -260,8 +260,6 @@ public final class BlockStateValues {
}
/**
* Non-water cauldrons (since Bedrock 1.18.30) must have a block entity packet sent on chunk load to fix rendering issues.
*
* @return if this Java block state is a non-empty non-water cauldron
*/
public static boolean isNonWaterCauldron(int state) {
@ -269,6 +267,8 @@ public final class BlockStateValues {
}
/**
* Cauldrons (since Bedrock 1.18.30) must have a block entity packet sent on chunk load to fix rendering issues.
* <p>
* When using a bucket on a cauldron sending a ServerboundUseItemPacket can result in the liquid being placed.
*
* @return if this Java block state is a cauldron

View File

@ -0,0 +1,302 @@
/*
* 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.network;
import io.netty.buffer.ByteBuf;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodecHelper;
import org.cloudburstmc.protocol.bedrock.codec.BedrockPacketSerializer;
import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.MobArmorEquipmentSerializer_v291;
import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.MobEquipmentSerializer_v291;
import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.PlayerHotbarSerializer_v291;
import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.SetEntityLinkSerializer_v291;
import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.SetEntityMotionSerializer_v291;
import org.cloudburstmc.protocol.bedrock.codec.v390.serializer.PlayerSkinSerializer_v390;
import org.cloudburstmc.protocol.bedrock.codec.v407.serializer.InventoryContentSerializer_v407;
import org.cloudburstmc.protocol.bedrock.codec.v407.serializer.InventorySlotSerializer_v407;
import org.cloudburstmc.protocol.bedrock.codec.v486.serializer.BossEventSerializer_v486;
import org.cloudburstmc.protocol.bedrock.codec.v557.serializer.SetEntityDataSerializer_v557;
import org.cloudburstmc.protocol.bedrock.codec.v662.serializer.SetEntityMotionSerializer_v662;
import org.cloudburstmc.protocol.bedrock.packet.AnvilDamagePacket;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.cloudburstmc.protocol.bedrock.packet.BossEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.ClientCacheBlobStatusPacket;
import org.cloudburstmc.protocol.bedrock.packet.ClientCacheStatusPacket;
import org.cloudburstmc.protocol.bedrock.packet.ClientCheatAbilityPacket;
import org.cloudburstmc.protocol.bedrock.packet.ClientToServerHandshakePacket;
import org.cloudburstmc.protocol.bedrock.packet.CodeBuilderSourcePacket;
import org.cloudburstmc.protocol.bedrock.packet.CraftingEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.CreatePhotoPacket;
import org.cloudburstmc.protocol.bedrock.packet.DebugInfoPacket;
import org.cloudburstmc.protocol.bedrock.packet.EditorNetworkPacket;
import org.cloudburstmc.protocol.bedrock.packet.EntityFallPacket;
import org.cloudburstmc.protocol.bedrock.packet.GameTestRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.InventoryContentPacket;
import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket;
import org.cloudburstmc.protocol.bedrock.packet.LabTablePacket;
import org.cloudburstmc.protocol.bedrock.packet.MapCreateLockedCopyPacket;
import org.cloudburstmc.protocol.bedrock.packet.MapInfoRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.MobArmorEquipmentPacket;
import org.cloudburstmc.protocol.bedrock.packet.MobEquipmentPacket;
import org.cloudburstmc.protocol.bedrock.packet.MultiplayerSettingsPacket;
import org.cloudburstmc.protocol.bedrock.packet.NpcRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.PhotoInfoRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.PhotoTransferPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerHotbarPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerSkinPacket;
import org.cloudburstmc.protocol.bedrock.packet.PurchaseReceiptPacket;
import org.cloudburstmc.protocol.bedrock.packet.RefreshEntitlementsPacket;
import org.cloudburstmc.protocol.bedrock.packet.ScriptMessagePacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket;
import org.cloudburstmc.protocol.bedrock.packet.SettingsCommandPacket;
import org.cloudburstmc.protocol.bedrock.packet.SimpleEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.SubChunkRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.SubClientLoginPacket;
import org.cloudburstmc.protocol.bedrock.packet.TickSyncPacket;
import org.cloudburstmc.protocol.common.util.VarInts;
/**
* Processes the Bedrock codec to remove or modify unused or unsafe packets and fields.
*/
class CodecProcessor {
/**
* Generic serializer that throws an exception when trying to serialize or deserialize a packet, leading to client disconnection.
*/
@SuppressWarnings("rawtypes")
private static final BedrockPacketSerializer ILLEGAL_SERIALIZER = new BedrockPacketSerializer<>() {
@Override
public void serialize(ByteBuf buffer, BedrockCodecHelper helper, BedrockPacket packet) {
throw new IllegalArgumentException("Server tried to send unused packet " + packet.getClass().getSimpleName() + "!");
}
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, BedrockPacket packet) {
throw new IllegalArgumentException("Client tried to send unused packet " + packet.getClass().getSimpleName() + "!");
}
};
/**
* Generic serializer that does nothing when trying to serialize or deserialize a packet.
*/
@SuppressWarnings("rawtypes")
private static final BedrockPacketSerializer IGNORED_SERIALIZER = new BedrockPacketSerializer<>() {
@Override
public void serialize(ByteBuf buffer, BedrockCodecHelper helper, BedrockPacket packet) {
}
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, BedrockPacket packet) {
}
};
/**
* Serializer that throws an exception when trying to deserialize InventoryContentPacket since server-auth inventory is used.
*/
private static final BedrockPacketSerializer<InventoryContentPacket> INVENTORY_CONTENT_SERIALIZER = new InventoryContentSerializer_v407() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventoryContentPacket packet) {
throw new IllegalArgumentException("Client cannot send InventoryContentPacket in server-auth inventory environment!");
}
};
/**
* Serializer that throws an exception when trying to deserialize InventorySlotPacket since server-auth inventory is used.
*/
private static final BedrockPacketSerializer<InventorySlotPacket> INVENTORY_SLOT_SERIALIZER = new InventorySlotSerializer_v407() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventorySlotPacket packet) {
throw new IllegalArgumentException("Client cannot send InventorySlotPacket in server-auth inventory environment!");
}
};
/**
* Serializer that does nothing when trying to deserialize BossEventPacket since it is not used from the client.
*/
private static final BedrockPacketSerializer<BossEventPacket> BOSS_EVENT_SERIALIZER = new BossEventSerializer_v486() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, BossEventPacket packet) {
}
};
/**
* Serializer that does nothing when trying to deserialize MobArmorEquipmentPacket since it is not used from the client.
*/
private static final BedrockPacketSerializer<MobArmorEquipmentPacket> MOB_ARMOR_EQUIPMENT_SERIALIZER = new MobArmorEquipmentSerializer_v291() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, MobArmorEquipmentPacket packet) {
}
};
/**
* Serializer that does nothing when trying to deserialize PlayerHotbarPacket since it is not used from the client.
*/
private static final BedrockPacketSerializer<PlayerHotbarPacket> PLAYER_HOTBAR_SERIALIZER = new PlayerHotbarSerializer_v291() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, PlayerHotbarPacket packet) {
}
};
/**
* Serializer that does nothing when trying to deserialize PlayerSkinPacket since it is not used from the client.
*/
private static final BedrockPacketSerializer<PlayerSkinPacket> PLAYER_SKIN_SERIALIZER = new PlayerSkinSerializer_v390() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, PlayerSkinPacket packet) {
}
};
/**
* Serializer that does nothing when trying to deserialize SetEntityDataPacket since it is not used from the client.
*/
private static final BedrockPacketSerializer<SetEntityDataPacket> SET_ENTITY_DATA_SERIALIZER = new SetEntityDataSerializer_v557() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetEntityDataPacket packet) {
}
};
/**
* Serializer that does nothing when trying to deserialize SetEntityMotionPacket since it is not used from the client for codec v291.
*/
private static final BedrockPacketSerializer<SetEntityMotionPacket> SET_ENTITY_MOTION_SERIALIZER_V291 = new SetEntityMotionSerializer_v291() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetEntityMotionPacket packet) {
}
};
/**
* Serializer that does nothing when trying to deserialize SetEntityMotionPacket since it is not used from the client for codec v662.
*/
private static final BedrockPacketSerializer<SetEntityMotionPacket> SET_ENTITY_MOTION_SERIALIZER_V662 = new SetEntityMotionSerializer_v662() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetEntityMotionPacket packet) {
}
};
/**
* Serializer that does nothing when trying to deserialize SetEntityLinkPacket since it is not used from the client.
*/
private static final BedrockPacketSerializer<SetEntityLinkPacket> SET_ENTITY_LINK_SERIALIZER = new SetEntityLinkSerializer_v291() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetEntityLinkPacket packet) {
}
};
/**
* Serializer that skips over the item when trying to deserialize MobEquipmentPacket since only the slot info is used.
*/
private static final BedrockPacketSerializer<MobEquipmentPacket> MOB_EQUIPMENT_SERIALIZER = new MobEquipmentSerializer_v291() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, MobEquipmentPacket packet) {
packet.setRuntimeEntityId(VarInts.readUnsignedLong(buffer));
fakeItemRead(buffer);
packet.setInventorySlot(buffer.readUnsignedByte());
packet.setHotbarSlot(buffer.readUnsignedByte());
packet.setContainerId(buffer.readByte());
}
};
@SuppressWarnings("unchecked")
static BedrockCodec processCodec(BedrockCodec codec) {
return codec.toBuilder()
// Illegal unused serverbound EDU packets
.updateSerializer(PhotoTransferPacket.class, ILLEGAL_SERIALIZER)
.updateSerializer(LabTablePacket.class, ILLEGAL_SERIALIZER)
.updateSerializer(CodeBuilderSourcePacket.class, ILLEGAL_SERIALIZER)
.updateSerializer(CreatePhotoPacket.class, ILLEGAL_SERIALIZER)
.updateSerializer(NpcRequestPacket.class, ILLEGAL_SERIALIZER)
.updateSerializer(PhotoInfoRequestPacket.class, ILLEGAL_SERIALIZER)
// Illegal unused serverbound packets for featured servers
.updateSerializer(PurchaseReceiptPacket.class, ILLEGAL_SERIALIZER)
// Illegal unused serverbound packets that are deprecated
.updateSerializer(ClientCheatAbilityPacket.class, ILLEGAL_SERIALIZER)
// Illegal unusued serverbound packets that relate to unused features
.updateSerializer(PlayerAuthInputPacket.class, ILLEGAL_SERIALIZER)
.updateSerializer(ClientCacheBlobStatusPacket.class, ILLEGAL_SERIALIZER)
.updateSerializer(SubClientLoginPacket.class, ILLEGAL_SERIALIZER)
.updateSerializer(SubChunkRequestPacket.class, ILLEGAL_SERIALIZER)
.updateSerializer(GameTestRequestPacket.class, ILLEGAL_SERIALIZER)
// Ignored serverbound packets
.updateSerializer(CraftingEventPacket.class, IGNORED_SERIALIZER) // Make illegal when 1.20.40 is removed
.updateSerializer(ClientToServerHandshakePacket.class, IGNORED_SERIALIZER)
.updateSerializer(EntityFallPacket.class, IGNORED_SERIALIZER)
.updateSerializer(MapCreateLockedCopyPacket.class, IGNORED_SERIALIZER)
.updateSerializer(MapInfoRequestPacket.class, IGNORED_SERIALIZER)
.updateSerializer(SettingsCommandPacket.class, IGNORED_SERIALIZER)
.updateSerializer(AnvilDamagePacket.class, IGNORED_SERIALIZER)
.updateSerializer(RefreshEntitlementsPacket.class, IGNORED_SERIALIZER)
// Illegal when serverbound due to Geyser specific setup
.updateSerializer(InventoryContentPacket.class, INVENTORY_CONTENT_SERIALIZER)
.updateSerializer(InventorySlotPacket.class, INVENTORY_SLOT_SERIALIZER)
// Ignored only when serverbound
.updateSerializer(BossEventPacket.class, BOSS_EVENT_SERIALIZER)
.updateSerializer(MobArmorEquipmentPacket.class, MOB_ARMOR_EQUIPMENT_SERIALIZER)
.updateSerializer(PlayerHotbarPacket.class, PLAYER_HOTBAR_SERIALIZER)
.updateSerializer(PlayerSkinPacket.class, PLAYER_SKIN_SERIALIZER)
.updateSerializer(SetEntityDataPacket.class, SET_ENTITY_DATA_SERIALIZER)
.updateSerializer(SetEntityMotionPacket.class, codec.getProtocolVersion() < 662 ?
SET_ENTITY_MOTION_SERIALIZER_V291 :
SET_ENTITY_MOTION_SERIALIZER_V662)
.updateSerializer(SetEntityLinkPacket.class, SET_ENTITY_LINK_SERIALIZER)
// Valid serverbound packets where reading of some fields can be skipped
.updateSerializer(MobEquipmentPacket.class, MOB_EQUIPMENT_SERIALIZER)
// // Illegal bidirectional packets
.updateSerializer(DebugInfoPacket.class, ILLEGAL_SERIALIZER)
.updateSerializer(EditorNetworkPacket.class, ILLEGAL_SERIALIZER)
.updateSerializer(ScriptMessagePacket.class, ILLEGAL_SERIALIZER)
// // Ignored bidirectional packets
.updateSerializer(ClientCacheStatusPacket.class, IGNORED_SERIALIZER)
.updateSerializer(SimpleEventPacket.class, IGNORED_SERIALIZER)
.updateSerializer(TickSyncPacket.class, IGNORED_SERIALIZER)
.updateSerializer(MultiplayerSettingsPacket.class, IGNORED_SERIALIZER)
.build();
}
/**
* Fake reading an item from the buffer to improve performance.
*
* @param buffer
*/
private static void fakeItemRead(ByteBuf buffer) {
int id = VarInts.readInt(buffer); // Runtime ID
if (id == 0) { // nothing more to read
return;
}
buffer.skipBytes(2); // count
VarInts.readUnsignedInt(buffer); // damage
boolean hasNetId = buffer.readBoolean();
if (hasNetId) {
VarInts.readInt(buffer);
}
VarInts.readInt(buffer); // Block runtime ID
int streamSize = VarInts.readUnsignedInt(buffer);
buffer.skipBytes(streamSize);
}
}

View File

@ -33,6 +33,7 @@ import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630;
import org.cloudburstmc.protocol.bedrock.codec.v649.Bedrock_v649;
import org.cloudburstmc.protocol.bedrock.codec.v662.Bedrock_v662;
import org.cloudburstmc.protocol.bedrock.codec.v671.Bedrock_v671;
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
import org.geysermc.geyser.session.GeyserSession;
@ -44,11 +45,12 @@ import java.util.StringJoiner;
* Contains information about the supported protocols in Geyser.
*/
public final class GameProtocol {
/**
* Default Bedrock codec that should act as a fallback. Should represent the latest available
* release of the game that Geyser supports.
*/
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v662.CODEC;
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(Bedrock_v671.CODEC);
/**
* A list of all supported Bedrock versions that can join Geyser
@ -62,18 +64,21 @@ public final class GameProtocol {
private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC;
static {
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v622.CODEC.toBuilder()
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v622.CODEC.toBuilder()
.minecraftVersion("1.20.40/1.20.41")
.build());
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v630.CODEC.toBuilder()
.build()));
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v630.CODEC.toBuilder()
.minecraftVersion("1.20.50/1.20.51")
.build());
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v649.CODEC.toBuilder()
.build()));
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v649.CODEC.toBuilder()
.minecraftVersion("1.20.60/1.20.62")
.build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
.minecraftVersion("1.20.70/1.20.71")
.build());
.build()));
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v662.CODEC.toBuilder()
.minecraftVersion("1.20.70/1.20.73")
.build()));
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(DEFAULT_BEDROCK_CODEC.toBuilder()
.minecraftVersion("1.20.80")
.build()));
}
/**

View File

@ -25,12 +25,16 @@
package org.geysermc.geyser.network;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.protocol.bedrock.BedrockPeer;
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
import org.cloudburstmc.protocol.bedrock.netty.initializer.BedrockServerInitializer;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.event.bedrock.SessionInitializeEvent;
@ -63,6 +67,10 @@ public class GeyserServerInitializer extends BedrockServerInitializer {
bedrockServerSession.setLogging(true);
GeyserSession session = new GeyserSession(this.geyser, bedrockServerSession, this.eventLoopGroup.next());
Channel channel = bedrockServerSession.getPeer().getChannel();
channel.pipeline().addAfter(BedrockPacketCodec.NAME, InvalidPacketHandler.NAME, new InvalidPacketHandler(session));
bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(this.geyser, session));
this.geyser.eventBus().fire(new SessionInitializeEvent(session));
} catch (Throwable e) {

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
* of this software and associated documentation files (the "Software"), to deal
@ -23,28 +23,36 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.translator.protocol.bedrock;
package org.geysermc.geyser.network;
import org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.MapInfoRequestPacket;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.RequiredArgsConstructor;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
@Translator(packet = MapInfoRequestPacket.class)
public class BedrockMapInfoRequestTranslator extends PacketTranslator<MapInfoRequestPacket> {
@RequiredArgsConstructor
public class InvalidPacketHandler extends ChannelInboundHandlerAdapter {
public static final String NAME = "rak-error-handler";
private final GeyserSession session;
@Override
public void translate(GeyserSession session, MapInfoRequestPacket packet) {
long mapId = packet.getUniqueMapId();
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
Throwable rootCause = Stream.iterate(cause, Throwable::getCause)
.filter(element -> element.getCause() == null)
.findFirst()
.orElse(cause);
ClientboundMapItemDataPacket mapPacket = session.getStoredMaps().remove(mapId);
if (mapPacket != null) {
// Delay the packet 100ms to prevent the client from ignoring the packet
session.scheduleInEventLoop(() -> session.sendUpstreamPacket(mapPacket),
100, TimeUnit.MILLISECONDS);
if (!(rootCause instanceof IllegalArgumentException)) {
super.exceptionCaught(ctx, cause);
return;
}
// Kick users that try to send illegal packets
session.getGeyser().getLogger().warning(rootCause.getMessage());
session.disconnect("Invalid packet received!");
}
}

View File

@ -891,4 +891,9 @@ public class LoggingPacketHandler implements BedrockPacketHandler {
public PacketSignal handle(ToggleCrafterSlotRequestPacket packet) {
return defaultHandler(packet);
}
@Override
public PacketSignal handle(TrimDataPacket packet) {
return defaultHandler(packet);
}
}

View File

@ -49,6 +49,7 @@ public final class Bootstraps {
String kernelVersion;
try {
kernelVersion = Native.KERNEL_VERSION;
GeyserImpl.getInstance().getLogger().debug("Kernel version: " + kernelVersion);
} catch (Throwable e) {
GeyserImpl.getInstance().getLogger().debug("Could not determine kernel version! " + e.getMessage());
kernelVersion = null;
@ -67,10 +68,22 @@ public final class Bootstraps {
}
@SuppressWarnings({"rawtypes, unchecked"})
public static void setupBootstrap(AbstractBootstrap bootstrap) {
public static boolean setupBootstrap(AbstractBootstrap bootstrap) {
boolean success = true;
if (REUSEPORT_AVAILABLE) {
bootstrap.option(UnixChannelOption.SO_REUSEPORT, true);
// Guessing whether so_reuseport is available based on kernel version is cool, but unreliable.
Channel channel = bootstrap.register().channel();
if (channel.config().setOption(UnixChannelOption.SO_REUSEPORT, true)) {
bootstrap.option(UnixChannelOption.SO_REUSEPORT, true);
} else {
// If this occurs, we guessed wrong and reuseport is not available
GeyserImpl.getInstance().getLogger().debug("so_reuseport is not available despite version being " + Native.KERNEL_VERSION);
success = false;
}
// Now yeet that channel
channel.close();
}
return success;
}
private static int[] fromString(String input) {

View File

@ -25,7 +25,6 @@
package org.geysermc.geyser.network.netty;
import com.github.steveice10.packetlib.helper.TransportHelper;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
@ -39,6 +38,9 @@ import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.incubator.channel.uring.IOUring;
import io.netty.incubator.channel.uring.IOUringDatagramChannel;
import io.netty.incubator.channel.uring.IOUringEventLoopGroup;
import io.netty.util.concurrent.Future;
import lombok.Getter;
import net.jodah.expiringmap.ExpirationPolicy;
@ -46,8 +48,10 @@ import net.jodah.expiringmap.ExpiringMap;
import org.cloudburstmc.netty.channel.raknet.RakChannelFactory;
import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption;
import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerOfflineHandler;
import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerRateLimiter;
import org.cloudburstmc.protocol.bedrock.BedrockPong;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.event.connection.ConnectionRequestEvent;
import org.geysermc.geyser.command.defaults.ConnectionTestCommand;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.event.type.GeyserBedrockPingEventImpl;
@ -71,6 +75,9 @@ import java.util.concurrent.TimeUnit;
import java.util.function.IntFunction;
import java.util.function.Supplier;
import static org.cloudburstmc.netty.channel.raknet.RakConstants.DEFAULT_GLOBAL_PACKET_LIMIT;
import static org.cloudburstmc.netty.channel.raknet.RakConstants.DEFAULT_PACKET_LIMIT;
public final class GeyserServer {
private static final boolean PRINT_DEBUG_PINGS = Boolean.parseBoolean(System.getProperty("Geyser.PrintPingsInDebugMode", "true"));
@ -101,10 +108,14 @@ public final class GeyserServer {
@Getter
private final ExpiringMap<InetSocketAddress, InetSocketAddress> proxiedAddresses;
private final int listenCount;
private int listenCount;
private ChannelFuture[] bootstrapFutures;
// Keep track of connection attempts for dump info
@Getter
private int connectionAttempts = 0;
/**
* The port to broadcast in the pong. This can be different from the port the server is bound to, e.g. due to port forwarding.
*/
@ -118,8 +129,11 @@ public final class GeyserServer {
this.childGroup = TRANSPORT.eventLoopGroupFactory().apply(threadCount);
this.bootstrap = this.createBootstrap();
// setup SO_REUSEPORT if exists
Bootstraps.setupBootstrap(this.bootstrap);
// setup SO_REUSEPORT if exists - or, if the option does not actually exist, reset listen count
// otherwise, we try to bind multiple times which wont work if so_reuseport is not valid
if (!Bootstraps.setupBootstrap(this.bootstrap)) {
this.listenCount = 1;
}
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
this.proxiedAddresses = ExpiringMap.builder()
@ -141,23 +155,31 @@ public final class GeyserServer {
bootstrapFutures = new ChannelFuture[listenCount];
for (int i = 0; i < listenCount; i++) {
ChannelFuture future = bootstrap.bind(address);
addHandlers(future);
modifyHandlers(future);
bootstrapFutures[i] = future;
}
return Bootstraps.allOf(bootstrapFutures);
}
private void addHandlers(ChannelFuture future) {
private void modifyHandlers(ChannelFuture future) {
Channel channel = future.channel();
// Add our ping handler
channel.pipeline()
.addFirst(RakConnectionRequestHandler.NAME, new RakConnectionRequestHandler(this))
.addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this));
// Add proxy handler
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
boolean isProxyProtocol = this.geyser.getConfig().getBedrock().isEnableProxyProtocol();
if (isProxyProtocol) {
channel.pipeline().addFirst("proxy-protocol-decoder", new ProxyServerHandler());
}
boolean isWhitelistedProxyProtocol = isProxyProtocol && !this.geyser.getConfig().getBedrock().getProxyProtocolWhitelistedIPs().isEmpty();
if (Boolean.parseBoolean(System.getProperty("Geyser.RakRateLimitingDisabled", "false")) || isWhitelistedProxyProtocol) {
// We would already block any non-whitelisted IP addresses in onConnectionRequest so we can remove the rate limiter
channel.pipeline().remove(RakServerRateLimiter.NAME);
}
}
public void shutdown() {
@ -199,11 +221,24 @@ public final class GeyserServer {
GeyserServerInitializer serverInitializer = new GeyserServerInitializer(this.geyser);
playerGroup = serverInitializer.getEventLoopGroup();
this.geyser.getLogger().debug("Setting MTU to " + this.geyser.getConfig().getMtu());
int rakPacketLimit = positivePropOrDefault("Geyser.RakPacketLimit", DEFAULT_PACKET_LIMIT);
this.geyser.getLogger().debug("Setting RakNet packet limit to " + rakPacketLimit);
int rakGlobalPacketLimit = positivePropOrDefault("Geyser.RakGlobalPacketLimit", DEFAULT_GLOBAL_PACKET_LIMIT);
this.geyser.getLogger().debug("Setting RakNet global packet limit to " + rakGlobalPacketLimit);
boolean rakSendCookie = Boolean.parseBoolean(System.getProperty("Geyser.RakSendCookie", "true"));
this.geyser.getLogger().debug("Setting RakNet send cookie to " + rakSendCookie);
return new ServerBootstrap()
.channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannel()))
.group(group, childGroup)
.option(RakChannelOption.RAK_HANDLE_PING, true)
.option(RakChannelOption.RAK_MAX_MTU, this.geyser.getConfig().getMtu())
.option(RakChannelOption.RAK_PACKET_LIMIT, rakPacketLimit)
.option(RakChannelOption.RAK_GLOBAL_PACKET_LIMIT, rakGlobalPacketLimit)
.option(RakChannelOption.RAK_SEND_COOKIE, rakSendCookie)
.childHandler(serverInitializer);
}
@ -219,6 +254,7 @@ public final class GeyserServer {
}
if (!isWhitelistedIP) {
connectionAttempts++;
return false;
}
}
@ -233,7 +269,20 @@ public final class GeyserServer {
} else {
ip = "<IP address withheld>";
}
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.attempt_connect", ip));
ConnectionRequestEvent requestEvent = new ConnectionRequestEvent(
inetSocketAddress,
this.proxiedAddresses != null ? this.proxiedAddresses.get(inetSocketAddress) : null
);
geyser.eventBus().fire(requestEvent);
if (requestEvent.isCancelled()) {
geyser.getLogger().debug("Connection request from " + ip + " was cancelled using the API!");
connectionAttempts++;
return false;
}
geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.attempt_connect", ip));
connectionAttempts++;
return true;
}
@ -352,23 +401,57 @@ public final class GeyserServer {
}
}
private static int positivePropOrDefault(String property, int defaultValue) {
String value = System.getProperty(property);
try {
int parsed = value != null ? Integer.parseInt(value) : defaultValue;
if (parsed < 1) {
GeyserImpl.getInstance().getLogger().warning(
"Non-postive integer value for " + property + ": " + value + ". Using default value: " + defaultValue
);
return defaultValue;
}
return parsed;
} catch (NumberFormatException e) {
GeyserImpl.getInstance().getLogger().warning(
"Invalid integer value for " + property + ": " + value + ". Using default value: " + defaultValue
);
return defaultValue;
}
}
private static Transport compatibleTransport() {
TransportHelper.TransportMethod transportMethod = TransportHelper.determineTransportMethod();
if (transportMethod == TransportHelper.TransportMethod.EPOLL) {
if (isClassAvailable("io.netty.incubator.channel.uring.IOUring")
&& IOUring.isAvailable()
&& Boolean.parseBoolean(System.getProperty("Geyser.io_uring"))) {
return new Transport(IOUringDatagramChannel.class, IOUringEventLoopGroup::new);
}
if (isClassAvailable("io.netty.channel.epoll.Epoll") && Epoll.isAvailable()) {
return new Transport(EpollDatagramChannel.class, EpollEventLoopGroup::new);
}
if (transportMethod == TransportHelper.TransportMethod.KQUEUE) {
if (isClassAvailable("io.netty.channel.kqueue.KQueue") && KQueue.isAvailable()) {
return new Transport(KQueueDatagramChannel.class, KQueueEventLoopGroup::new);
}
// if (transportMethod == TransportHelper.TransportMethod.IO_URING) {
// return new Transport(IOUringDatagramChannel.class, IOUringEventLoopGroup::new);
// }
return new Transport(NioDatagramChannel.class, NioEventLoopGroup::new);
}
private record Transport(Class<? extends DatagramChannel> datagramChannel, IntFunction<EventLoopGroup> eventLoopGroupFactory) {
}
/**
* Used so implementations can opt to remove these dependencies if so desired
*/
private static boolean isClassAvailable(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}

View File

@ -43,6 +43,7 @@ import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630;
import org.cloudburstmc.protocol.bedrock.codec.v649.Bedrock_v649;
import org.cloudburstmc.protocol.bedrock.codec.v662.Bedrock_v662;
import org.cloudburstmc.protocol.bedrock.codec.v671.Bedrock_v671;
import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.geysermc.geyser.GeyserImpl;
@ -122,7 +123,8 @@ public final class BlockRegistryPopulator {
.put(ObjectIntPair.of("1_20_50", Bedrock_v630.CODEC.getProtocolVersion()), Conversion649_630::remapBlock)
// Only changes in 1.20.60 are hard_stained_glass (an EDU only block)
.put(ObjectIntPair.of("1_20_60", Bedrock_v649.CODEC.getProtocolVersion()), Conversion662_649::remapBlock)
.put(ObjectIntPair.of("1_20_70", Bedrock_v662.CODEC.getProtocolVersion()), tag -> tag)
.put(ObjectIntPair.of("1_20_70", Bedrock_v662.CODEC.getProtocolVersion()), Conversion671_662::remapBlock)
.put(ObjectIntPair.of("1_20_80", Bedrock_v671.CODEC.getProtocolVersion()), tag -> tag)
.build();
// We can keep this strong as nothing should be garbage collected
@ -144,7 +146,7 @@ public final class BlockRegistryPopulator {
builder.remove("version"); // Remove all nbt tags which are not needed for differentiating states
builder.remove("name_hash"); // Quick workaround - was added in 1.19.20
builder.remove("network_id"); // Added in 1.19.80 - ????
builder.remove("block_id"); // Added in 1.20.60 //TODO verify this can be just removed
builder.remove("block_id"); // Added in 1.20.60
//noinspection UnstableApiUsage
builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states")));
vanillaBlockStates.set(i, builder.build());
@ -229,6 +231,7 @@ public final class BlockRegistryPopulator {
Map<NbtMap, BlockDefinition> itemFrames = new Object2ObjectOpenHashMap<>();
Set<BlockDefinition> jigsawDefinitions = new ObjectOpenHashSet<>();
Map<String, BlockDefinition> structureBlockDefinitions = new Object2ObjectOpenHashMap<>();
BlockMappings.BlockMappingsBuilder builder = BlockMappings.builder();
while (blocksIterator.hasNext()) {
@ -272,6 +275,18 @@ public final class BlockRegistryPopulator {
jigsawDefinitions.add(bedrockDefinition);
}
if (javaId.contains("structure_block")) {
int modeIndex = javaId.indexOf("mode=");
if (modeIndex != -1) {
int startIndex = modeIndex + 5; // Length of "mode=" is 5
int endIndex = javaId.indexOf("]", startIndex);
if (endIndex != -1) {
String modeValue = javaId.substring(startIndex, endIndex);
structureBlockDefinitions.put(modeValue.toUpperCase(), bedrockDefinition);
}
}
}
boolean waterlogged = entry.getKey().contains("waterlogged=true")
|| javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass");
@ -358,6 +373,7 @@ public final class BlockRegistryPopulator {
.itemFrames(itemFrames)
.flowerPotBlocks(flowerPotBlocks)
.jigsawStates(jigsawDefinitions)
.structureBlockStates(structureBlockDefinitions)
.remappedVanillaIds(remappedVanillaIds)
.blockProperties(customBlockProperties)
.customBlockStateDefinitions(customBlockStateDefinitions)

View File

@ -44,6 +44,8 @@ public class Conversion662_649 {
static GeyserMappingItem remapItem(@SuppressWarnings("unused") Item item, GeyserMappingItem mapping) {
mapping = Conversion671_662.remapItem(item, mapping);
String identifer = mapping.getBedrockIdentifier();
if (identifer.equals("minecraft:grass_block")) {
@ -93,6 +95,8 @@ public class Conversion662_649 {
}
static NbtMap remapBlock(NbtMap tag) {
tag = Conversion671_662.remapBlock(tag);
final String name = tag.getString("name");
if (!NEW_BLOCKS.contains(name)) {

View File

@ -0,0 +1,182 @@
/*
* 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.registry.populator;
import org.cloudburstmc.nbt.NbtMap;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.registry.type.GeyserMappingItem;
import java.util.List;
import java.util.stream.Stream;
public class Conversion671_662 {
private static final List<String> NEW_CORAL_FANS = List.of("minecraft:tube_coral_fan", "minecraft:brain_coral_fan", "minecraft:bubble_coral_fan", "minecraft:fire_coral_fan", "minecraft:horn_coral_fan");
private static final List<String> NEW_DEAD_CORAL_FANS = List.of("minecraft:dead_tube_coral_fan", "minecraft:dead_brain_coral_fan", "minecraft:dead_bubble_coral_fan", "minecraft:dead_fire_coral_fan", "minecraft:dead_horn_coral_fan");
private static final List<String> NEW_FLOWERS = List.of("minecraft:poppy", "minecraft:blue_orchid", "minecraft:allium", "minecraft:azure_bluet", "minecraft:red_tulip", "minecraft:orange_tulip", "minecraft:white_tulip", "minecraft:pink_tulip", "minecraft:oxeye_daisy", "minecraft:cornflower", "minecraft:lily_of_the_valley");
private static final List<String> NEW_SAPLINGS = List.of("minecraft:oak_sapling", "minecraft:spruce_sapling", "minecraft:birch_sapling", "minecraft:jungle_sapling", "minecraft:acacia_sapling", "minecraft:dark_oak_sapling", "minecraft:bamboo_sapling");
private static final List<String> NEW_BLOCKS = Stream.of(NEW_CORAL_FANS, NEW_DEAD_CORAL_FANS, NEW_FLOWERS, NEW_SAPLINGS).flatMap(List::stream).toList();
static GeyserMappingItem remapItem(@SuppressWarnings("unused") Item item, GeyserMappingItem mapping) {
String identifer = mapping.getBedrockIdentifier();
if (!NEW_BLOCKS.contains(identifer)) {
return mapping;
}
if (NEW_FLOWERS.contains(identifer)) {
switch (identifer) {
case "minecraft:poppy" -> { return mapping.withBedrockIdentifier("minecraft:red_flower").withBedrockData(0); }
case "minecraft:blue_orchid" -> { return mapping.withBedrockIdentifier("minecraft:red_flower").withBedrockData(1); }
case "minecraft:allium" -> { return mapping.withBedrockIdentifier("minecraft:red_flower").withBedrockData(2); }
case "minecraft:azure_bluet" -> { return mapping.withBedrockIdentifier("minecraft:red_flower").withBedrockData(3); }
case "minecraft:red_tulip" -> { return mapping.withBedrockIdentifier("minecraft:red_flower").withBedrockData(4); }
case "minecraft:orange_tulip" -> { return mapping.withBedrockIdentifier("minecraft:red_flower").withBedrockData(5); }
case "minecraft:white_tulip" -> { return mapping.withBedrockIdentifier("minecraft:red_flower").withBedrockData(6); }
case "minecraft:pink_tulip" -> { return mapping.withBedrockIdentifier("minecraft:red_flower").withBedrockData(7); }
case "minecraft:oxeye_daisy" -> { return mapping.withBedrockIdentifier("minecraft:red_flower").withBedrockData(8); }
case "minecraft:cornflower" -> { return mapping.withBedrockIdentifier("minecraft:red_flower").withBedrockData(9); }
case "minecraft:lily_of_the_valley" -> { return mapping.withBedrockIdentifier("minecraft:red_flower").withBedrockData(10); }
}
}
if (NEW_SAPLINGS.contains(identifer)) {
switch (identifer) {
case "minecraft:oak_sapling" -> { return mapping.withBedrockIdentifier("minecraft:sapling").withBedrockData(0); }
case "minecraft:spruce_sapling" -> { return mapping.withBedrockIdentifier("minecraft:sapling").withBedrockData(1); }
case "minecraft:birch_sapling" -> { return mapping.withBedrockIdentifier("minecraft:sapling").withBedrockData(2); }
case "minecraft:jungle_sapling" -> { return mapping.withBedrockIdentifier("minecraft:sapling").withBedrockData(3); }
case "minecraft:acacia_sapling" -> { return mapping.withBedrockIdentifier("minecraft:sapling").withBedrockData(4); }
case "minecraft:dark_oak_sapling" -> { return mapping.withBedrockIdentifier("minecraft:sapling").withBedrockData(5); }
}
}
if (NEW_CORAL_FANS.contains(identifer)) {
switch (identifer) {
case "minecraft:tube_coral_fan" -> { return mapping.withBedrockIdentifier("minecraft:coral_fan").withBedrockData(0); }
case "minecraft:brain_coral_fan" -> { return mapping.withBedrockIdentifier("minecraft:coral_fan").withBedrockData(1); }
case "minecraft:bubble_coral_fan" -> { return mapping.withBedrockIdentifier("minecraft:coral_fan").withBedrockData(2); }
case "minecraft:fire_coral_fan" -> { return mapping.withBedrockIdentifier("minecraft:coral_fan").withBedrockData(3); }
case "minecraft:horn_coral_fan" -> { return mapping.withBedrockIdentifier("minecraft:coral_fan").withBedrockData(4); }
}
}
if (NEW_DEAD_CORAL_FANS.contains(identifer)) {
switch (identifer) {
case "minecraft:dead_tube_coral_fan" -> { return mapping.withBedrockIdentifier("minecraft:coral_fan_dead").withBedrockData(0); }
case "minecraft:dead_brain_coral_fan" -> { return mapping.withBedrockIdentifier("minecraft:coral_fan_dead").withBedrockData(1); }
case "minecraft:dead_bubble_coral_fan" -> { return mapping.withBedrockIdentifier("minecraft:coral_fan_dead").withBedrockData(2); }
case "minecraft:dead_fire_coral_fan" -> { return mapping.withBedrockIdentifier("minecraft:coral_fan_dead").withBedrockData(3); }
case "minecraft:dead_horn_coral_fan" -> { return mapping.withBedrockIdentifier("minecraft:coral_fan_dead").withBedrockData(4); }
}
}
return mapping;
}
static NbtMap remapBlock(NbtMap tag) {
final String name = tag.getString("name");
if (!NEW_BLOCKS.contains(name)) {
return tag;
}
if (name.equals("minecraft:bamboo_sapling")) {
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("sapling_type", "oak")
.build();
return tag.toBuilder().putCompound("states", states).build();
}
String replacement;
if (NEW_SAPLINGS.contains(name)) {
replacement = "minecraft:sapling";
String saplingType = name.replaceAll("minecraft:|_sapling", "");;
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("sapling_type", saplingType)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_FLOWERS.contains(name)) {
replacement = "minecraft:red_flower";
String flowerType;
switch (name) {
case "minecraft:poppy" -> flowerType = "poppy";
case "minecraft:blue_orchid" -> flowerType = "orchid";
case "minecraft:allium" -> flowerType = "allium";
case "minecraft:azure_bluet" -> flowerType = "houstonia";
case "minecraft:red_tulip" -> flowerType = "tulip_red";
case "minecraft:orange_tulip" -> flowerType = "tulip_orange";
case "minecraft:white_tulip" -> flowerType = "tulip_white";
case "minecraft:pink_tulip" -> flowerType = "tulip_pink";
case "minecraft:oxeye_daisy" -> flowerType = "oxeye";
case "minecraft:cornflower" -> flowerType = "cornflower";
case "minecraft:lily_of_the_valley" -> flowerType = "lily_of_the_valley";
default -> throw new IllegalStateException("Unexpected value: " + name);
}
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("flower_type", flowerType)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
boolean isLiveCoralFan = NEW_CORAL_FANS.contains(name);
boolean isDeadCoralFan = NEW_DEAD_CORAL_FANS.contains(name);
if (isLiveCoralFan || isDeadCoralFan) {
replacement = isLiveCoralFan ? "minecraft:coral_fan" : "minecraft:coral_fan_dead";
String coralColor;
switch (name) {
case "minecraft:tube_coral_fan", "minecraft:dead_tube_coral_fan" -> coralColor = "blue";
case "minecraft:brain_coral_fan", "minecraft:dead_brain_coral_fan" -> coralColor = "pink";
case "minecraft:bubble_coral_fan", "minecraft:dead_bubble_coral_fan" -> coralColor = "purple";
case "minecraft:fire_coral_fan", "minecraft:dead_fire_coral_fan" -> coralColor = "yellow";
case "minecraft:horn_coral_fan", "minecraft:dead_horn_coral_fan" -> coralColor = "red";
default -> throw new IllegalStateException("Unexpected value: " + name);
}
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("coral_color", coralColor)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
return tag;
}
}

View File

@ -1,3 +1,28 @@
/*
* 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.registry.populator;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@ -80,7 +105,6 @@ public class CustomBlockRegistryPopulator {
}
private static Set<CustomBlockData> CUSTOM_BLOCKS;
private static Set<String> CUSTOM_BLOCK_NAMES;
private static Map<String, CustomBlockData> CUSTOM_BLOCK_ITEM_OVERRIDES;
private static Map<JavaBlockState, CustomBlockState> NON_VANILLA_BLOCK_STATE_OVERRIDES;
private static Map<String, CustomBlockState> BLOCK_STATE_OVERRIDES_QUEUE;
@ -90,19 +114,19 @@ public class CustomBlockRegistryPopulator {
*/
private static void populateBedrock() {
CUSTOM_BLOCKS = new ObjectOpenHashSet<>();
CUSTOM_BLOCK_NAMES = new ObjectOpenHashSet<>();
CUSTOM_BLOCK_ITEM_OVERRIDES = new HashMap<>();
NON_VANILLA_BLOCK_STATE_OVERRIDES = new HashMap<>();
BLOCK_STATE_OVERRIDES_QUEUE = new HashMap<>();
Set<String> customBlockIdentifiers = new ObjectOpenHashSet<>();
GeyserImpl.getInstance().getEventBus().fire(new GeyserDefineCustomBlocksEvent() {
@Override
public void register(@NonNull CustomBlockData customBlockData) {
if (customBlockData.name().isEmpty()) {
throw new IllegalArgumentException("Custom block name must have at least 1 character.");
}
if (!CUSTOM_BLOCK_NAMES.add(customBlockData.name())) {
throw new IllegalArgumentException("Another custom block was already registered under the name: " + customBlockData.name());
if (!customBlockIdentifiers.add(customBlockData.identifier())) {
throw new IllegalArgumentException("Another custom block was already registered under the identifier: " + customBlockData.identifier());
}
if (Character.isDigit(customBlockData.name().charAt(0))) {
throw new IllegalArgumentException("Custom block can not start with a digit. Name: " + customBlockData.name());

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
* of this software and associated documentation files (the "Software"), to deal
@ -153,7 +153,7 @@ public class CustomItemRegistryPopulator {
.build();
NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId,
customItemData.creativeCategory(), customItemData.creativeGroup(), customItemData.isHat(), customItemData.displayHandheld(), protocolVersion);
customItemData.isHat(), customItemData.displayHandheld(), protocolVersion);
ComponentItemData componentItemData = new ComponentItemData(customIdentifier, builder.build());
return new NonVanillaItemRegistration(componentItemData, item, customItemMapping);
@ -172,7 +172,7 @@ public class CustomItemRegistryPopulator {
boolean canDestroyInCreative = true;
if (mapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here.
canDestroyInCreative = computeToolProperties(mapping.getToolType(), itemProperties, componentBuilder);
canDestroyInCreative = computeToolProperties(mapping.getToolType(), itemProperties, componentBuilder, javaItem.attackDamage());
}
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
@ -208,10 +208,8 @@ public class CustomItemRegistryPopulator {
return builder;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customItemData, String customItemName,
int customItemId, OptionalInt creativeCategory,
String creativeGroup, boolean isHat, boolean displayHandheld, int protocolVersion) {
int customItemId, boolean isHat, boolean displayHandheld, int protocolVersion) {
NbtMapBuilder builder = NbtMap.builder();
builder.putString("name", customItemName)
.putInt("id", customItemId);
@ -223,7 +221,7 @@ public class CustomItemRegistryPopulator {
boolean canDestroyInCreative = true;
if (customItemData.toolType() != null) { // This is not using the isTool boolean because it is not just a render type here.
canDestroyInCreative = computeToolProperties(Objects.requireNonNull(customItemData.toolType()), itemProperties, componentBuilder);
canDestroyInCreative = computeToolProperties(Objects.requireNonNull(customItemData.toolType()), itemProperties, componentBuilder, customItemData.attackDamage());
}
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
@ -250,6 +248,11 @@ public class CustomItemRegistryPopulator {
itemProperties.putBoolean("foil", true);
}
String block = customItemData.block();
if (block != null) {
computeBlockItemProperties(block, componentBuilder);
}
componentBuilder.putCompound("item_properties", itemProperties.build());
builder.putCompound("components", componentBuilder.build());
@ -311,7 +314,7 @@ public class CustomItemRegistryPopulator {
/**
* @return can destroy in creative
*/
private static boolean computeToolProperties(String toolType, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
private static boolean computeToolProperties(String toolType, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int attackDamage) {
boolean canDestroyInCreative = true;
float miningSpeed = 1.0f;
@ -362,6 +365,11 @@ public class CustomItemRegistryPopulator {
itemProperties.putInt("enchantable_value", 1);
itemProperties.putString("enchantable_slot", toolType);
// Adds a "attack damage" indicator. Purely visual!
if (attackDamage > 0) {
itemProperties.putInt("damage", attackDamage);
}
return canDestroyInCreative;
}

View File

@ -42,6 +42,7 @@ import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630;
import org.cloudburstmc.protocol.bedrock.codec.v649.Bedrock_v649;
import org.cloudburstmc.protocol.bedrock.codec.v662.Bedrock_v662;
import org.cloudburstmc.protocol.bedrock.codec.v671.Bedrock_v671;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
@ -93,7 +94,8 @@ public class ItemRegistryPopulator {
paletteVersions.add(new PaletteVersion("1_20_40", Bedrock_v622.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion630_622::remapItem));
paletteVersions.add(new PaletteVersion("1_20_50", Bedrock_v630.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion649_630::remapItem));
paletteVersions.add(new PaletteVersion("1_20_60", Bedrock_v649.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion662_649::remapItem));
paletteVersions.add(new PaletteVersion("1_20_70", Bedrock_v662.CODEC.getProtocolVersion()));
paletteVersions.add(new PaletteVersion("1_20_70", Bedrock_v662.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion671_662::remapItem));
paletteVersions.add(new PaletteVersion("1_20_80", Bedrock_v671.CODEC.getProtocolVersion()));
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();

View File

@ -173,7 +173,7 @@ public class RecipeRegistryPopulator {
/* Convert end */
return ShapedRecipeData.shaped(uuid.toString(), shape.get(0).length(), shape.size(),
inputs.stream().map(ItemDescriptorWithCount::fromItem).toList(), Collections.singletonList(output), uuid, "crafting_table", 0, netId);
inputs.stream().map(ItemDescriptorWithCount::fromItem).toList(), Collections.singletonList(output), uuid, "crafting_table", 0, netId, false);
}
List<ItemData> inputs = new ObjectArrayList<>();
for (JsonNode entry : node.get("inputs")) {

View File

@ -61,6 +61,7 @@ public class BlockMappings implements DefinitionRegistry<GeyserBedrockBlock> {
Map<String, NbtMap> flowerPotBlocks;
Set<BlockDefinition> jigsawStates;
Map<String, BlockDefinition> structureBlockStates;
List<BlockPropertyData> blockProperties;
Object2ObjectMap<CustomBlockState, GeyserBedrockBlock> customBlockStateDefinitions;
@ -96,6 +97,10 @@ public class BlockMappings implements DefinitionRegistry<GeyserBedrockBlock> {
return false;
}
public BlockDefinition getStructureBlockFromMode(String mode) {
return structureBlockStates.get(mode);
}
@Override
public @Nullable GeyserBedrockBlock getDefinition(int bedrockId) {
if (bedrockId < 0 || bedrockId >= this.bedrockRuntimeMap.length) {

View File

@ -25,13 +25,16 @@
package org.geysermc.geyser.scoreboard;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.NumberFormat;
import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@Getter
@ -47,6 +50,7 @@ public final class Objective {
private ScoreboardPosition displaySlot;
private String displaySlotName;
private String displayName = "unknown";
private NumberFormat numberFormat;
private int type = 0; // 0 = integer, 1 = heart
private Map<String, Score> scores = new ConcurrentHashMap<>();
@ -85,25 +89,29 @@ public final class Objective {
};
}
public void registerScore(String id, int score) {
public void registerScore(String id, int score, Component displayName, NumberFormat numberFormat) {
if (!scores.containsKey(id)) {
long scoreId = scoreboard.getNextId().getAndIncrement();
Score scoreObject = new Score(scoreId, id)
.setScore(score)
.setTeam(scoreboard.getTeamFor(id))
.setDisplayName(displayName)
.setNumberFormat(numberFormat)
.setUpdateType(UpdateType.ADD);
scores.put(id, scoreObject);
}
}
public void setScore(String id, int score) {
public void setScore(String id, int score, Component displayName, NumberFormat numberFormat) {
Score stored = scores.get(id);
if (stored != null) {
stored.setScore(score)
.setDisplayName(displayName)
.setNumberFormat(numberFormat)
.setUpdateType(UpdateType.UPDATE);
return;
}
registerScore(id, score);
registerScore(id, score, displayName, numberFormat);
}
public void removeScore(String id) {
@ -128,6 +136,26 @@ public final class Objective {
return this;
}
public Objective setNumberFormat(NumberFormat numberFormat) {
if (Objects.equals(this.numberFormat, numberFormat)) {
return this;
}
this.numberFormat = numberFormat;
if (updateType == UpdateType.NOTHING) {
updateType = UpdateType.UPDATE;
}
// Update the number format for scores that are following this objective's number format
for (Score score : scores.values()) {
if (score.getNumberFormat() == null) {
score.setUpdateType(UpdateType.UPDATE);
}
}
return this;
}
public Objective setType(int type) {
this.type = type;
if (updateType == UpdateType.NOTHING) {

View File

@ -25,9 +25,16 @@
package org.geysermc.geyser.scoreboard;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.FixedFormat;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.NumberFormat;
import net.kyori.adventure.text.Component;
import org.cloudburstmc.protocol.bedrock.data.ScoreInfo;
import lombok.Getter;
import lombok.experimental.Accessors;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.text.MessageTranslator;
import java.util.Objects;
@Getter
@Accessors(chain = true)
@ -52,6 +59,10 @@ public final class Score {
}
public String getDisplayName() {
String displayName = cachedData.displayName;
if (displayName != null) {
return displayName;
}
Team team = cachedData.team;
if (team != null) {
return team.getDisplayName(name);
@ -88,6 +99,35 @@ public final class Score {
return this;
}
public Score setDisplayName(Component displayName) {
if (currentData.displayName != null && displayName != null) {
String convertedDisplayName = MessageTranslator.convertMessage(displayName);
if (!currentData.displayName.equals(convertedDisplayName)) {
currentData.displayName = convertedDisplayName;
setUpdateType(UpdateType.UPDATE);
}
return this;
}
// simplified from (this.displayName != null && displayName == null) || (this.displayName == null && displayName != null)
if (currentData.displayName != null || displayName != null) {
currentData.displayName = MessageTranslator.convertMessage(displayName);
setUpdateType(UpdateType.UPDATE);
}
return this;
}
public NumberFormat getNumberFormat() {
return currentData.numberFormat;
}
public Score setNumberFormat(NumberFormat numberFormat) {
if (!Objects.equals(currentData.numberFormat, numberFormat)) {
currentData.numberFormat = numberFormat;
setUpdateType(UpdateType.UPDATE);
}
return this;
}
public UpdateType getUpdateType() {
return currentData.updateType;
}
@ -105,7 +145,7 @@ public final class Score {
(currentData.team != null && currentData.team.shouldUpdate());
}
public void update(String objectiveName) {
public void update(Objective objective) {
if (cachedData == null) {
cachedData = new ScoreData();
cachedData.updateType = UpdateType.ADD;
@ -119,13 +159,26 @@ public final class Score {
currentData.changed = false;
cachedData.team = currentData.team;
cachedData.score = currentData.score;
cachedData.displayName = currentData.displayName;
cachedData.numberFormat = currentData.numberFormat;
String name = this.name;
if (cachedData.team != null) {
if (cachedData.displayName != null) {
name = cachedData.displayName;
} else if (cachedData.team != null) {
cachedData.team.prepareUpdate();
name = cachedData.team.getDisplayName(name);
}
cachedInfo = new ScoreInfo(id, objectiveName, cachedData.score, name);
NumberFormat numberFormat = cachedData.numberFormat;
if (numberFormat == null) {
numberFormat = objective.getNumberFormat();
}
if (numberFormat instanceof FixedFormat fixedFormat) {
name += " " + ChatColor.RESET + MessageTranslator.convertMessage(fixedFormat.getValue());
}
cachedInfo = new ScoreInfo(id, objective.getObjectiveName(), cachedData.score, name);
}
@Getter
@ -136,6 +189,9 @@ public final class Score {
private Team team;
private int score;
private String displayName;
private NumberFormat numberFormat;
private ScoreData() {
updateType = UpdateType.ADD;
}

View File

@ -240,7 +240,7 @@ public final class Scoreboard {
boolean update = score.shouldUpdate();
if (update) {
score.update(objective.getObjectiveName());
score.update(objective);
}
if (score.getUpdateType() != REMOVE && update) {
@ -281,7 +281,7 @@ public final class Scoreboard {
}
if (score.shouldUpdate()) {
score.update(objective.getObjectiveName());
score.update(objective);
add = true;
}

View File

@ -214,6 +214,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private final PistonCache pistonCache;
private final PreferencesCache preferencesCache;
private final SkullCache skullCache;
private final StructureBlockCache structureBlockCache;
private final TagCache tagCache;
private final WorldCache worldCache;
@ -261,8 +262,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Setter
private ItemMappings itemMappings;
private final Long2ObjectMap<ClientboundMapItemDataPacket> storedMaps = new Long2ObjectOpenHashMap<>();
/**
* Required to decode biomes correctly.
*/
@ -625,6 +624,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
this.pistonCache = new PistonCache(this);
this.preferencesCache = new PreferencesCache(this);
this.skullCache = new SkullCache(this);
this.structureBlockCache = new StructureBlockCache();
this.tagCache = new TagCache();
this.worldCache = new WorldCache(this);
this.cameraData = new GeyserCameraData(this);
@ -712,7 +712,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
// Default move speed
// Bedrock clients move very fast by default until they get an attribute packet correcting the speed
attributesPacket.setAttributes(Collections.singletonList(
new AttributeData("minecraft:movement", 0.0f, 1024f, 0.1f, 0.1f)));
GeyserAttributeType.MOVEMENT_SPEED.getAttribute()));
upstream.sendPacket(attributesPacket);
GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket();
@ -1167,11 +1167,15 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
/**
* Schedules a task and prints a stack trace if an error occurs.
* <p>
* The task will not run if the session is closed.
*/
public ScheduledFuture<?> scheduleInEventLoop(Runnable runnable, long duration, TimeUnit timeUnit) {
return eventLoop.schedule(() -> {
try {
runnable.run();
if (!closed) {
runnable.run();
}
} catch (Throwable e) {
geyser.getLogger().error("Error thrown in " + this.bedrockUsername() + "'s event loop!", e);
}

View File

@ -85,27 +85,29 @@ public class EntityCache {
return false;
}
public boolean removeEntity(Entity entity, boolean force) {
public void removeEntity(Entity entity) {
if (entity instanceof PlayerEntity player) {
session.getPlayerWithCustomHeads().remove(player.getUuid());
}
if (entity != null && entity.isValid() && (force || entity.despawnEntity())) {
if (entity != null) {
if (entity.isValid()) {
entity.despawnEntity();
}
long geyserId = entityIdTranslations.remove(entity.getEntityId());
entities.remove(geyserId);
if (entity instanceof Tickable) {
tickableEntities.remove(entity);
}
return true;
}
return false;
}
public void removeAllEntities() {
List<Entity> entities = new ArrayList<>(this.entities.values());
for (Entity entity : entities) {
removeEntity(entity, false);
removeEntity(entity);
}
session.getPlayerWithCustomHeads().clear();

View File

@ -0,0 +1,59 @@
/*
* 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.session.cache;
import lombok.Getter;
import lombok.Setter;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3i;
@Setter
@Getter
public final class StructureBlockCache {
/**
* Stores the current structure's name to be able to detect changes in the loaded structure
*/
private @Nullable String currentStructureName;
/**
* Stores the offset changes added by Geyser that ensure that structure bounds
* are the same for Java and Bedrock
*/
private @Nullable Vector3i bedrockOffset;
/**
* Stores the current structure block position while we're waiting on the Java
* server to send the data we need.
*/
private @Nullable Vector3i currentStructureBlock;
public void clear() {
this.currentStructureName = null;
this.currentStructureBlock = null;
this.bedrockOffset = null;
}
}

View File

@ -535,7 +535,7 @@ public class SkinProvider {
private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(imageUrl).openConnection();
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION);
con.setRequestProperty("User-Agent", WebUtils.getUserAgent());
con.setConnectTimeout(10000);
con.setReadTimeout(10000);

View File

@ -63,7 +63,7 @@ public interface BedrockOnlyBlockEntity extends RequiresBlockState {
return FlowerPotBlockEntityTranslator.getTag(session, blockState, position);
} else if (PistonBlockEntityTranslator.isBlock(blockState)) {
return PistonBlockEntityTranslator.getTag(blockState, position);
} else if (BlockStateValues.isNonWaterCauldron(blockState)) {
} else if (BlockStateValues.isCauldron(blockState)) {
// As of 1.18.30: this is required to make rendering not look weird on chunk load (lava and snow cauldrons look dim)
return NbtMap.builder()
.putString("id", "Cauldron")

View File

@ -0,0 +1,138 @@
/*
* 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.translator.level.block.entity;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.protocol.bedrock.data.structure.StructureMirror;
import org.cloudburstmc.protocol.bedrock.data.structure.StructureRotation;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.StructureBlockUtils;
@BlockEntity(type = BlockEntityType.STRUCTURE_BLOCK)
public class StructureBlockBlockEntityTranslator extends BlockEntityTranslator {
@Override
public NbtMap getBlockEntityTag(GeyserSession session, BlockEntityType type, int x, int y, int z, CompoundTag tag, int blockState) {
// Sending a structure with size 0 doesn't clear the outline. Hence, we have to force it by replacing the block :/
int xStructureSize = getOrDefault(tag.get("sizeX"), 0);
int yStructureSize = getOrDefault(tag.get("sizeY"), 0);
int zStructureSize = getOrDefault(tag.get("sizeZ"), 0);
Vector3i size = Vector3i.from(xStructureSize, yStructureSize, zStructureSize);
if (size.equals(Vector3i.ZERO)) {
Vector3i position = Vector3i.from(x, y, z);
String mode = getOrDefault(tag.get("mode"), "");
// Set to air and back to reset the structure block
UpdateBlockPacket emptyBlockPacket = new UpdateBlockPacket();
emptyBlockPacket.setDataLayer(0);
emptyBlockPacket.setBlockPosition(position);
emptyBlockPacket.setDefinition(session.getBlockMappings().getBedrockAir());
session.sendUpstreamPacket(emptyBlockPacket);
UpdateBlockPacket spawnerBlockPacket = new UpdateBlockPacket();
spawnerBlockPacket.setDataLayer(0);
spawnerBlockPacket.setBlockPosition(position);
spawnerBlockPacket.setDefinition(session.getBlockMappings().getStructureBlockFromMode(mode));
session.sendUpstreamPacket(spawnerBlockPacket);
}
return super.getBlockEntityTag(session, type, x, y, z, tag, blockState);
}
@Override
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
if (tag.size() < 5) {
return; // These values aren't here
}
builder.putString("structureName", getOrDefault(tag.get("name"), ""));
String mode = getOrDefault(tag.get("mode"), "");
int bedrockData = switch (mode) {
case "LOAD" -> 2;
case "CORNER" -> 3;
case "DATA" -> 4;
default -> 1; // SAVE
};
builder.putInt("data", bedrockData);
builder.putString("dataField", ""); // ??? possibly related to Java's "metadata"
// Mirror behaves different in Java and Bedrock - it requires modifying the position in space as well
String mirror = getOrDefault(tag.get("mirror"), "");
StructureMirror bedrockMirror = switch (mirror) {
case "FRONT_BACK" -> StructureMirror.X;
case "LEFT_RIGHT" -> StructureMirror.Z;
default -> StructureMirror.NONE;
};
builder.putByte("mirror", (byte) bedrockMirror.ordinal());
builder.putByte("ignoreEntities", getOrDefault(tag.get("ignoreEntities"), (byte) 0));
builder.putByte("isPowered", getOrDefault(tag.get("powered"), (byte) 0));
builder.putLong("seed", getOrDefault(tag.get("seed"), 0L));
builder.putByte("showBoundingBox", getOrDefault(tag.get("showboundingbox"), (byte) 0));
String rotation = getOrDefault(tag.get("rotation"), "");
StructureRotation bedrockRotation = switch (rotation) {
case "CLOCKWISE_90" -> StructureRotation.ROTATE_90;
case "CLOCKWISE_180" -> StructureRotation.ROTATE_180;
case "COUNTERCLOCKWISE_90" -> StructureRotation.ROTATE_270;
default -> StructureRotation.NONE;
};
builder.putByte("rotation", (byte) bedrockRotation.ordinal());
int xStructureSize = getOrDefault(tag.get("sizeX"), 0);
int yStructureSize = getOrDefault(tag.get("sizeY"), 0);
int zStructureSize = getOrDefault(tag.get("sizeZ"), 0);
// The "positions" are also offsets on Java
int posX = getOrDefault(tag.get("posX"), 0);
int posY = getOrDefault(tag.get("posY"), 0);
int posZ = getOrDefault(tag.get("posZ"), 0);
Vector3i offset = StructureBlockUtils.calculateOffset(bedrockRotation, bedrockMirror,
xStructureSize, zStructureSize);
builder.putInt("xStructureOffset", posX + offset.getX());
builder.putInt("yStructureOffset", posY);
builder.putInt("zStructureOffset", posZ + offset.getZ());
builder.putInt("xStructureSize", xStructureSize);
builder.putInt("yStructureSize", yStructureSize);
builder.putInt("zStructureSize", zStructureSize);
builder.putFloat("integrity", getOrDefault(tag.get("integrity"), 0f)); // Is 1.0f by default on Java but 100.0f on Bedrock
// Java's "showair" is unrepresented
}
}

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
* of this software and associated documentation files (the "Software"), to deal
@ -62,13 +62,17 @@ public class BedrockEntityPickRequestTranslator extends PacketTranslator<EntityP
case 2 -> "birch";
case 3 -> "jungle";
case 4 -> "acacia";
//case 5 -> "cherry"; TODO
case 5 -> "cherry";
case 6 -> "dark_oak";
case 7 -> "mangrove";
//case 8 -> "bamboo";
case 8 -> "bamboo";
default -> "oak";
};
itemName = typeOfBoat + "_" + entity.getDefinition().entityType().name().toLowerCase(Locale.ROOT);
// Bamboo boat is a raft
if (variant == 8) {
itemName = itemName.replace("boat", "raft");
}
}
case LEASH_KNOT -> itemName = "lead";
case CHEST_MINECART, COMMAND_BLOCK_MINECART, FURNACE_MINECART, HOPPER_MINECART, TNT_MINECART ->

View File

@ -348,6 +348,13 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
openPacket.setType(ContainerType.JIGSAW_EDITOR);
openPacket.setUniqueEntityId(-1);
session.sendUpstreamPacket(openPacket);
} else if (session.getBlockMappings().getStructureBlockStates().containsValue(packet.getBlockDefinition())) {
ContainerOpenPacket openPacket = new ContainerOpenPacket();
openPacket.setBlockPosition(packet.getBlockPosition());
openPacket.setId((byte) 1);
openPacket.setType(ContainerType.STRUCTURE_EDITOR);
openPacket.setUniqueEntityId(-1);
session.sendUpstreamPacket(openPacket);
}
}
}

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.translator.protocol.bedrock;
import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockAction;
import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockMode;
import org.cloudburstmc.protocol.bedrock.data.structure.StructureBlockType;
import org.cloudburstmc.protocol.bedrock.data.structure.StructureEditorData;
import org.cloudburstmc.protocol.bedrock.packet.StructureBlockUpdatePacket;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.StructureBlockUtils;
@Translator(packet = StructureBlockUpdatePacket.class)
public class BedrockStructureBlockUpdateTranslator extends PacketTranslator<StructureBlockUpdatePacket> {
@Override
public void translate(GeyserSession session, StructureBlockUpdatePacket packet) {
StructureEditorData data = packet.getEditorData();
UpdateStructureBlockAction action = UpdateStructureBlockAction.UPDATE_DATA;
if (packet.isPowered()) {
if (data.getType() == StructureBlockType.LOAD) {
action = UpdateStructureBlockAction.LOAD_STRUCTURE;
} else if (data.getType() == StructureBlockType.SAVE) {
action = UpdateStructureBlockAction.SAVE_STRUCTURE;
}
}
UpdateStructureBlockMode mode = switch (data.getType()) {
case CORNER -> UpdateStructureBlockMode.CORNER;
case DATA -> UpdateStructureBlockMode.DATA;
case LOAD -> UpdateStructureBlockMode.LOAD;
default -> UpdateStructureBlockMode.SAVE;
};
StructureBlockUtils.sendJavaStructurePacket(session, packet.getBlockPosition(), data.getSettings().getSize(), mode, action, data.getSettings(),
data.isBoundingBoxVisible(), data.getName());
session.getStructureBlockCache().clear();
}
}

View File

@ -0,0 +1,91 @@
/*
* 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.translator.protocol.bedrock;
import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockAction;
import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockMode;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.structure.StructureSettings;
import org.cloudburstmc.protocol.bedrock.data.structure.StructureTemplateRequestOperation;
import org.cloudburstmc.protocol.bedrock.packet.StructureTemplateDataRequestPacket;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.StructureBlockUtils;
/**
* Packet used in Bedrock to load structure size into the structure block GUI. It is sent every time the GUI is opened.
* Or, if the player updates the structure name. Which we can use to request the structure size from the Java server!
* <p>
* Java does not have this preview, instead, Java clients are forced out of the GUI to look at the area.
*/
@Translator(packet = StructureTemplateDataRequestPacket.class)
public class BedrockStructureTemplateDataRequestTranslator extends PacketTranslator<StructureTemplateDataRequestPacket> {
@Override
public void translate(GeyserSession session, StructureTemplateDataRequestPacket packet) {
// All other operation types are ignored by Geyser since we do not support exporting/importing structures
if (packet.getOperation().equals(StructureTemplateRequestOperation.QUERY_SAVED_STRUCTURE)) {
Vector3i size = packet.getSettings().getSize();
StructureSettings settings = packet.getSettings();
// If we send a load packet to the Java server when the structure size is known, it would place the structure.
String currentStructureName = session.getStructureBlockCache().getCurrentStructureName();
// Case 1: Opening a structure block with information about structure size, but not yet saved by us
// Case 2: Getting an update from Bedrock with new information, doesn't bother us if it's the same structure
if (!packet.getSettings().getSize().equals(Vector3i.ZERO)) {
if (currentStructureName == null) {
Vector3i offset = StructureBlockUtils.calculateOffset(settings.getRotation(), settings.getMirror(),
settings.getSize().getX(), settings.getSize().getZ());
session.getStructureBlockCache().setBedrockOffset(offset);
session.getStructureBlockCache().setCurrentStructureName(packet.getName());
StructureBlockUtils.sendStructureData(session, size, packet.getName());
return;
} else if (packet.getName().equals(currentStructureName)) {
StructureBlockUtils.sendStructureData(session, size, packet.getName());
return;
}
}
// Request a "structure load" from Java server, so it sends us the structure's size
// See the block entity translator for more info
session.getStructureBlockCache().setCurrentStructureBlock(packet.getPosition());
StructureBlockUtils.sendJavaStructurePacket(session,
packet.getPosition(),
Vector3i.ZERO, // We expect the Java server to tell us the size
UpdateStructureBlockMode.LOAD,
UpdateStructureBlockAction.LOAD_STRUCTURE,
settings,
true,
packet.getName()
);
} else {
StructureBlockUtils.sendEmptyStructureData(session);
}
}
}

View File

@ -163,7 +163,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
LevelEventPacket startBreak = new LevelEventPacket();
startBreak.setType(LevelEvent.BLOCK_START_BREAK);
startBreak.setPosition(vector.toFloat());
double breakTime = BlockUtils.getSessionBreakTime(session, BlockRegistries.JAVA_BLOCKS.get(blockState)) * 20;
double breakTime = BlockUtils.getSessionBreakTime(session, BlockRegistries.JAVA_BLOCKS.getOrDefault(blockState, BlockMapping.DEFAULT)) * 20;
// If the block is custom or the breaking item is custom, we must keep track of break time ourselves
GeyserItemStack item = session.getPlayerInventory().getItemInHand();
@ -215,7 +215,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
LevelEventPacket updateBreak = new LevelEventPacket();
updateBreak.setType(LevelEvent.BLOCK_UPDATE_BREAK);
updateBreak.setPosition(vectorFloat);
double breakTime = BlockUtils.getSessionBreakTime(session, BlockRegistries.JAVA_BLOCKS.get(breakingBlock)) * 20;
double breakTime = BlockUtils.getSessionBreakTime(session, BlockRegistries.JAVA_BLOCKS.getOrDefault(breakingBlock, BlockMapping.DEFAULT)) * 20;
// If the block is custom, we must keep track of when it should break ourselves

View File

@ -126,12 +126,7 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
} else {
InventoryUtils.openInventory(session, session.getPlayerInventory());
}
} else {
// Case: Player tries to open a player inventory, while we think it should be in a different inventory
// Now: Open the inventory that we're supposed to be in.
InventoryUtils.openInventory(session, session.getOpenInventory());
}
break;
}
}
}

View File

@ -71,7 +71,7 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
// Remove all bossbars
session.getEntityCache().removeAllBossBars();
// Remove extra hearts, hunger, etc.
entity.getAttributes().clear();
entity.resetAttributes();
entity.resetMetadata();
// Reset weather

View File

@ -49,6 +49,14 @@ public class JavaRespawnTranslator extends PacketTranslator<ClientboundRespawnPa
SessionPlayerEntity entity = session.getPlayerEntity();
PlayerSpawnInfo spawnInfo = packet.getCommonPlayerSpawnInfo();
if (!packet.isKeepMetadata()) {
entity.resetMetadata();
}
if (!packet.isKeepAttributes()) {
entity.resetAttributes();
}
session.setSpawned(false);
entity.setHealth(entity.getMaxHealth());

View File

@ -162,7 +162,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
bedrockRecipeIDs.add(uuid.toString());
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData.shaped(uuid.toString(),
shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), Arrays.asList(inputs),
Collections.singletonList(output), uuid, "crafting_table", 0, netId));
Collections.singletonList(output), uuid, "crafting_table", 0, netId, false));
recipeMap.put(netId++, new GeyserShapedRecipe(shapedRecipeData));
}
addRecipeIdentifier(session, recipe.getIdentifier(), bedrockRecipeIDs);

View File

@ -39,7 +39,7 @@ public class JavaRemoveEntitiesTranslator extends PacketTranslator<ClientboundRe
for (int entityId : packet.getEntityIds()) {
Entity entity = session.getEntityCache().getEntityByJavaId(entityId);
if (entity != null) {
session.getEntityCache().removeEntity(entity, false);
session.getEntityCache().removeEntity(entity);
}
}
}

View File

@ -193,7 +193,8 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
uuid,
"crafting_table",
0,
newRecipeId
newRecipeId,
false
));
craftPacket.setCleanRecipes(false);
session.sendUpstreamPacket(craftPacket);

View File

@ -30,6 +30,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.BlockMapping;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
@ -42,7 +43,7 @@ public class JavaBlockDestructionTranslator extends PacketTranslator<Clientbound
@Override
public void translate(GeyserSession session, ClientboundBlockDestructionPacket packet) {
int state = session.getGeyser().getWorldManager().getBlockAt(session, packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ());
int breakTime = (int) (65535 / Math.ceil(BlockUtils.getBreakTime(session, BlockRegistries.JAVA_BLOCKS.get(state), ItemMapping.AIR, new CompoundTag(""), false) * 20));
int breakTime = (int) (65535 / Math.ceil(BlockUtils.getBreakTime(session, BlockRegistries.JAVA_BLOCKS.getOrDefault(state, BlockMapping.DEFAULT), ItemMapping.AIR, new CompoundTag(""), false) * 20));
LevelEventPacket levelEventPacket = new LevelEventPacket();
levelEventPacket.setPosition(packet.getPosition().toFloat());
levelEventPacket.setType(LevelEvent.BLOCK_START_BREAK);

View File

@ -28,9 +28,13 @@ package org.geysermc.geyser.translator.protocol.java.level;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundBlockEntityDataPacket;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.data.structure.StructureMirror;
import org.cloudburstmc.protocol.bedrock.data.structure.StructureRotation;
import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
import org.geysermc.geyser.level.block.BlockStateValues;
@ -41,6 +45,7 @@ import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTransla
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.BlockEntityUtils;
import org.geysermc.geyser.util.StructureBlockUtils;
@Translator(packet = ClientboundBlockEntityDataPacket.class)
public class JavaBlockEntityDataTranslator extends PacketTranslator<ClientboundBlockEntityDataPacket> {
@ -95,5 +100,59 @@ public class JavaBlockEntityDataTranslator extends PacketTranslator<ClientboundB
openPacket.setUniqueEntityId(-1);
session.sendUpstreamPacket(openPacket);
}
// When a Java client is trying to load a structure, it expects the server to send it the size of the structure.
// On 1.20.4, the server does so here - we can pass that through to Bedrock, so we're properly selecting the area.
if (type == BlockEntityType.STRUCTURE_BLOCK && session.getGameMode() == GameMode.CREATIVE
&& packet.getPosition().equals(session.getStructureBlockCache().getCurrentStructureBlock())
&& packet.getNbt() != null && packet.getNbt().size() > 5
) {
CompoundTag map = packet.getNbt();
String mode = getOrDefault(map.get("mode"), "");
if (!mode.equalsIgnoreCase("LOAD")) {
return;
}
String mirror = getOrDefault(map.get("mirror"), "");
StructureMirror bedrockMirror = switch (mirror) {
case "FRONT_BACK" -> StructureMirror.X;
case "LEFT_RIGHT" -> StructureMirror.Z;
default -> StructureMirror.NONE;
};
String rotation = getOrDefault(map.get("rotation"), "");
StructureRotation bedrockRotation = switch (rotation) {
case "CLOCKWISE_90" -> StructureRotation.ROTATE_90;
case "CLOCKWISE_180" -> StructureRotation.ROTATE_180;
case "COUNTERCLOCKWISE_90" -> StructureRotation.ROTATE_270;
default -> StructureRotation.NONE;
};
String name = getOrDefault(map.get("name"), "");
int sizeX = getOrDefault(map.get("sizeX"), 0);
int sizeY = getOrDefault(map.get("sizeY"), 0);
int sizeZ = getOrDefault(map.get("sizeZ"), 0);
session.getStructureBlockCache().setCurrentStructureBlock(null);
Vector3i size = Vector3i.from(sizeX, sizeY, sizeZ);
if (size.equals(Vector3i.ZERO)) {
StructureBlockUtils.sendEmptyStructureData(session);
return;
}
Vector3i offset = StructureBlockUtils.calculateOffset(bedrockRotation, bedrockMirror,
sizeX, sizeZ);
session.getStructureBlockCache().setBedrockOffset(offset);
session.getStructureBlockCache().setCurrentStructureName(name);
StructureBlockUtils.sendStructureData(session, size, name);
}
}
protected <T> T getOrDefault(Tag tag, T defaultValue) {
//noinspection unchecked
return (tag != null && tag.getValue() != null) ? (T) tag.getValue() : defaultValue;
}
}

View File

@ -200,7 +200,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
}
// Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock
if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId) || BlockStateValues.isNonWaterCauldron(javaId)) {
if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId) || BlockStateValues.isCauldron(javaId)) {
bedrockBlockEntities.add(BedrockOnlyBlockEntity.getTag(session,
Vector3i.from((packet.getX() << 4) + (yzx & 0xF), ((sectionY + yOffset) << 4) + ((yzx >> 8) & 0xF), (packet.getZ() << 4) + ((yzx >> 4) & 0xF)),
javaId
@ -259,7 +259,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
}
// Check if block is piston, flower or cauldron to see if we'll need to create additional block entities, as they're only block entities in Bedrock
if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId) || BlockStateValues.isNonWaterCauldron(javaId)) {
if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId) || BlockStateValues.isCauldron(javaId)) {
bedrockOnlyBlockEntityIds.set(i);
}
}

View File

@ -44,7 +44,6 @@ public class JavaMapItemDataTranslator extends PacketTranslator<ClientboundMapIt
@Override
public void translate(GeyserSession session, ClientboundMapItemDataPacket packet) {
org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket mapItemDataPacket = new org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket();
boolean shouldStore = false;
mapItemDataPacket.setUniqueMapId(packet.getMapId());
mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
@ -61,11 +60,6 @@ public class JavaMapItemDataTranslator extends PacketTranslator<ClientboundMapIt
mapItemDataPacket.setWidth(data.getColumns());
mapItemDataPacket.setHeight(data.getRows());
// We have a full map image, this usually only happens on spawn for the initial image
if (mapItemDataPacket.getWidth() == 128 && mapItemDataPacket.getHeight() == 128) {
shouldStore = true;
}
// Every int entry is an ARGB color
int[] colors = new int[data.getData().length];
@ -87,12 +81,11 @@ public class JavaMapItemDataTranslator extends PacketTranslator<ClientboundMapIt
id++;
}
// Store the map to send when the client requests it, as bedrock expects the data after a MapInfoRequestPacket
if (shouldStore) {
session.getStoredMaps().put(mapItemDataPacket.getUniqueMapId(), mapItemDataPacket);
// Client will ignore if sent too early
if (session.isSentSpawnPacket()) {
session.sendUpstreamPacket(mapItemDataPacket);
} else {
session.getUpstream().queuePostStartGamePacket(mapItemDataPacket);
}
// Send anyway just in case
session.sendUpstreamPacket(mapItemDataPacket);
}
}

View File

@ -54,7 +54,7 @@ public class JavaResetScorePacket extends PacketTranslator<ClientboundResetScore
// as described below
if (belowName != null) {
JavaSetScoreTranslator.setBelowName(session, belowName, packet.getOwner(), 0);
JavaSetScoreTranslator.setBelowName(session, belowName, packet.getOwner());
}
} else {
Objective objective = scoreboard.getObjective(packet.getObjective());
@ -64,7 +64,7 @@ public class JavaResetScorePacket extends PacketTranslator<ClientboundResetScore
// attached to this score.
if (objective == belowName) {
// Update the score on this player to now reflect 0
JavaSetScoreTranslator.setBelowName(session, objective, packet.getOwner(), 0);
JavaSetScoreTranslator.setBelowName(session, objective, packet.getOwner());
}
}

View File

@ -47,7 +47,6 @@ public class JavaSetObjectiveTranslator extends PacketTranslator<ClientboundSetO
@Override
public void translate(GeyserSession session, ClientboundSetObjectivePacket packet) {
// todo 1.20.3 unused NumberFormat ?
WorldCache worldCache = session.getWorldCache();
Scoreboard scoreboard = worldCache.getScoreboard();
int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond();
@ -64,8 +63,19 @@ public class JavaSetObjectiveTranslator extends PacketTranslator<ClientboundSetO
}
switch (packet.getAction()) {
case ADD, UPDATE -> objective.setDisplayName(MessageTranslator.convertMessage(packet.getDisplayName()))
.setType(packet.getType().ordinal());
case ADD, UPDATE -> {
objective.setDisplayName(MessageTranslator.convertMessage(packet.getDisplayName()))
.setNumberFormat(packet.getNumberFormat())
.setType(packet.getType().ordinal());
if (objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME)) {
// Update the score tag of all players
for (PlayerEntity entity : session.getEntityCache().getAllPlayerEntities()) {
if (entity.isValid()) {
entity.setBelowNameText(objective);
}
}
}
}
case REMOVE -> {
scoreboard.unregisterObjective(packet.getName());
if (objective != null && objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME)) {

View File

@ -28,8 +28,6 @@ package org.geysermc.geyser.translator.protocol.java.scoreboard;
import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
@ -54,7 +52,6 @@ public class JavaSetScoreTranslator extends PacketTranslator<ClientboundSetScore
@Override
public void translate(GeyserSession session, ClientboundSetScorePacket packet) {
// todo 1.20.3 unused display and number format?
WorldCache worldCache = session.getWorldCache();
Scoreboard scoreboard = worldCache.getScoreboard();
int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond();
@ -71,10 +68,10 @@ public class JavaSetScoreTranslator extends PacketTranslator<ClientboundSetScore
// attached to this score.
boolean isBelowName = objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME);
objective.setScore(packet.getOwner(), packet.getValue());
objective.setScore(packet.getOwner(), packet.getValue(), packet.getDisplay(), packet.getNumberFormat());
if (isBelowName) {
// Update the below name score on this player
setBelowName(session, objective, packet.getOwner(), packet.getValue());
setBelowName(session, objective, packet.getOwner());
}
// ScoreboardUpdater will handle it for us if the packets per second
@ -87,20 +84,13 @@ public class JavaSetScoreTranslator extends PacketTranslator<ClientboundSetScore
/**
* @param objective the objective that currently resides on the below name display slot
*/
static void setBelowName(GeyserSession session, Objective objective, String username, int count) {
static void setBelowName(GeyserSession session, Objective objective, String username) {
PlayerEntity entity = getOtherPlayerEntity(session, username);
if (entity == null) {
return;
}
String displayString = count + " " + objective.getDisplayName();
// Of note: unlike Bedrock, if there is an objective in the below name slot, everyone has a display
entity.getDirtyMetadata().put(EntityDataTypes.SCORE, displayString);
SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
entityDataPacket.setRuntimeEntityId(entity.getGeyserId());
entityDataPacket.getMetadata().put(EntityDataTypes.SCORE, displayString);
session.sendUpstreamPacket(entityDataPacket);
entity.setBelowNameText(objective);
}
private static @Nullable PlayerEntity getOtherPlayerEntity(GeyserSession session, String username) {

View File

@ -27,6 +27,7 @@ package org.geysermc.geyser.util;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.geysermc.geyser.GeyserBootstrap;
@ -56,6 +57,8 @@ public class FileUtils {
*/
public static <T> T loadConfig(File src, Class<T> valueType) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory())
// Allow inference of single values as arrays
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
return objectMapper.readValue(src, valueType);
}

View File

@ -125,7 +125,7 @@ public class InventoryUtils {
InventoryTranslator translator = session.getInventoryTranslator();
translator.closeInventory(session, inventory);
if (confirm && inventory.isDisplayed() && !inventory.isPending()
&& !(translator instanceof LecternInventoryTranslator) // TODO: double-check
&& !(translator instanceof LecternInventoryTranslator) // Closing lecterns is not followed with a close confirmation
) {
session.setClosingInventory(true);
}

View File

@ -0,0 +1,164 @@
/*
* 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.util;
import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockAction;
import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockMode;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetStructureBlockPacket;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtList;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType;
import org.cloudburstmc.protocol.bedrock.data.structure.StructureMirror;
import org.cloudburstmc.protocol.bedrock.data.structure.StructureRotation;
import org.cloudburstmc.protocol.bedrock.data.structure.StructureSettings;
import org.cloudburstmc.protocol.bedrock.data.structure.StructureTemplateResponseType;
import org.cloudburstmc.protocol.bedrock.packet.StructureTemplateDataResponsePacket;
import org.geysermc.geyser.session.GeyserSession;
public class StructureBlockUtils {
private static final NbtMap EMPTY_STRUCTURE_DATA;
static {
NbtMapBuilder builder = NbtMap.builder();
builder.putInt("format_version", 1);
builder.putCompound("structure", NbtMap.builder()
.putList("block_indices", NbtType.LIST, NbtList.EMPTY, NbtList.EMPTY)
.putList("entities", NbtType.COMPOUND)
.putCompound("palette", NbtMap.EMPTY)
.build());
builder.putList("structure_world_origin", NbtType.INT, 0, 0, 0);
EMPTY_STRUCTURE_DATA = builder.build();
}
public static void sendEmptyStructureData(GeyserSession session) {
StructureTemplateDataResponsePacket responsePacket = new StructureTemplateDataResponsePacket();
responsePacket.setName("");
responsePacket.setSave(false);
responsePacket.setType(StructureTemplateResponseType.QUERY);
session.sendUpstreamPacket(responsePacket);
}
public static void sendStructureData(GeyserSession session,Vector3i size, String name) {
StructureTemplateDataResponsePacket responsePacket = new StructureTemplateDataResponsePacket();
responsePacket.setName(name);
responsePacket.setSave(true);
responsePacket.setTag(EMPTY_STRUCTURE_DATA.toBuilder()
// Bedrock does not like negative sizes here
.putList("size", NbtType.INT, Math.abs(size.getX()), size.getY(), Math.abs(size.getZ()))
.build());
responsePacket.setType(StructureTemplateResponseType.QUERY);
session.sendUpstreamPacket(responsePacket);
}
public static Vector3i calculateOffset(StructureRotation structureRotation, StructureMirror structureMirror,
int sizeX, int sizeZ) {
int newOffsetX = 0;
int newOffsetZ = 0;
switch (structureRotation) {
case ROTATE_90 -> {
switch (structureMirror) {
case NONE -> newOffsetX -= sizeZ - 1;
case X -> {
newOffsetZ -= sizeX - 1;
newOffsetX -= sizeZ - 1;
}
}
}
case ROTATE_180 -> {
switch (structureMirror) {
case NONE -> {
newOffsetX -= sizeX - 1;
newOffsetZ -= sizeZ - 1;
}
case Z -> newOffsetX -= sizeX - 1;
case X -> newOffsetZ -= sizeZ - 1;
}
}
case ROTATE_270 -> {
switch (structureMirror) {
case NONE -> newOffsetZ -= sizeX - 1;
case Z -> {
newOffsetZ -= sizeX - 1;
newOffsetX -= sizeZ - 1;
}
}
}
default -> {
switch (structureMirror) {
case Z -> newOffsetZ -= sizeZ - 1;
case X -> newOffsetX -= sizeX - 1;
}
}
}
return Vector3i.from(newOffsetX, 0, newOffsetZ);
}
public static void sendJavaStructurePacket(GeyserSession session, Vector3i blockPosition, Vector3i size, UpdateStructureBlockMode mode, UpdateStructureBlockAction action,
StructureSettings settings, boolean boundingBoxVisible, String structureName) {
com.github.steveice10.mc.protocol.data.game.level.block.StructureMirror mirror = switch (settings.getMirror()) {
case X -> com.github.steveice10.mc.protocol.data.game.level.block.StructureMirror.FRONT_BACK;
case Z -> com.github.steveice10.mc.protocol.data.game.level.block.StructureMirror.LEFT_RIGHT;
default -> com.github.steveice10.mc.protocol.data.game.level.block.StructureMirror.NONE;
};
com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation rotation = switch (settings.getRotation()) {
case ROTATE_90 -> com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation.CLOCKWISE_90;
case ROTATE_180 -> com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation.CLOCKWISE_180;
case ROTATE_270 -> com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation.COUNTERCLOCKWISE_90;
default -> com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation.NONE;
};
Vector3i offset = settings.getOffset();
if (session.getStructureBlockCache().getBedrockOffset() != null) {
offset = settings.getOffset().sub(session.getStructureBlockCache().getBedrockOffset());
}
ServerboundSetStructureBlockPacket structureBlockPacket = new ServerboundSetStructureBlockPacket(
blockPosition,
action,
mode,
structureName,
offset,
settings.getSize(),
mirror,
rotation,
"",
settings.getIntegrityValue(),
settings.getIntegritySeed(),
settings.isIgnoringEntities(),
false,
boundingBoxVisible
);
session.sendDownstreamPacket(structureBlockPacket);
}
}

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
* of this software and associated documentation files (the "Software"), to deal
@ -40,6 +40,7 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Map;
import java.util.stream.Stream;
public class WebUtils {
@ -54,7 +55,7 @@ public class WebUtils {
URL url = new URL(reqURL);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION); // Otherwise Java 8 fails on checking updates
con.setRequestProperty("User-Agent", getUserAgent()); // Otherwise Java 8 fails on checking updates
con.setConnectTimeout(10000);
con.setReadTimeout(10000);
@ -72,7 +73,7 @@ public class WebUtils {
*/
public static JsonNode getJson(String reqURL) throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(reqURL).openConnection();
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION);
con.setRequestProperty("User-Agent", getUserAgent());
con.setConnectTimeout(10000);
con.setReadTimeout(10000);
return GeyserImpl.JSON_MAPPER.readTree(con.getInputStream());
@ -87,7 +88,7 @@ public class WebUtils {
public static void downloadFile(String reqURL, String fileLocation) {
try {
HttpURLConnection con = (HttpURLConnection) new URL(reqURL).openConnection();
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION);
con.setRequestProperty("User-Agent", getUserAgent());
InputStream in = con.getInputStream();
Files.copy(in, Paths.get(fileLocation), StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
@ -108,7 +109,7 @@ public class WebUtils {
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "text/plain");
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION);
con.setRequestProperty("User-Agent", getUserAgent());
con.setDoOutput(true);
OutputStream out = con.getOutputStream();
@ -163,7 +164,7 @@ public class WebUtils {
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION);
con.setRequestProperty("User-Agent", getUserAgent());
con.setDoOutput(true);
try (OutputStream out = con.getOutputStream()) {
@ -176,6 +177,13 @@ public class WebUtils {
return connectionToString(con);
}
/**
* Find a SRV record for the given address
*
* @param geyser Geyser instance
* @param remoteAddress Address to find the SRV record for
* @return The SRV record or null if not found
*/
public static String @Nullable [] findSrvRecord(GeyserImpl geyser, String remoteAddress) {
try {
// Searches for a server address and a port from a SRV record of the specified host name
@ -193,4 +201,30 @@ public class WebUtils {
}
return null;
}
/**
* Get a stream of lines from the given URL
*
* @param reqURL URL to fetch
* @return Stream of lines from the URL or an empty stream if the request fails
*/
public static Stream<String> getLineStream(String reqURL) {
try {
URL url = new URL(reqURL);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", getUserAgent()); // Otherwise Java 8 fails on checking updates
con.setConnectTimeout(10000);
con.setReadTimeout(10000);
return connectionToString(con).lines();
} catch (Exception e) {
GeyserImpl.getInstance().getLogger().error("Error while trying to get a stream from " + reqURL, e);
return Stream.empty();
}
}
public static String getUserAgent() {
return "Geyser-" + GeyserImpl.getInstance().getPlatformType().platformName() + "/" + GeyserImpl.VERSION;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -39,8 +39,8 @@ bedrock:
# A list of allowed PROXY protocol speaking proxy IP addresses/subnets. Only effective when "enable-proxy-protocol" is enabled, and
# should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.).
# Keeping this list empty means there is no IP address whitelist.
# Both IP addresses and subnets are supported.
#proxy-protocol-whitelisted-ips: [ "127.0.0.1", "172.18.0.0/16" ]
# IP addresses, subnets, and links to plain text files are supported.
#proxy-protocol-whitelisted-ips: [ "127.0.0.1", "172.18.0.0/16", "https://example.com/whitelist.txt" ]
remote:
# The IP address of the remote (Java Edition) server
# If it is "auto", for standalone version the remote address will be set to 127.0.0.1,

@ -1 +1 @@
Subproject commit b1883ca53afc082de98aeb062ba2fad00e069617
Subproject commit 4f89411d5bfb4e48699f635876d9c556e5c7e505

View File

@ -7,5 +7,5 @@ org.gradle.vfs.watch=false
group=org.geysermc
id=geyser
version=2.2.2-SNAPSHOT
description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers.
version=2.2.3-SNAPSHOT
description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers.

View File

@ -3,16 +3,17 @@ base-api = "1.0.0-SNAPSHOT"
cumulus = "1.1.2"
erosion = "1.0-20230406.174837-8"
events = "1.1-SNAPSHOT"
jackson = { strictly = "2.14.0" } # Don't let other dependencies override
jackson = "2.17.0"
fastutil = "8.5.2"
netty = "4.1.107.Final"
netty-io-uring = "0.0.25.Final-SNAPSHOT"
guava = "29.0-jre"
gson = "2.3.1" # Provided by Spigot 1.8.8
websocket = "1.5.1"
protocol = "3.0.0.Beta1-20240313.120922-126"
protocol-connection = "3.0.0.Beta1-20240313.120922-125"
raknet = "1.0.0.CR1-20231206.145325-12"
blockstateupdater="1.20.70-20240303.125052-2"
protocol = "3.0.0.Beta1-20240411.165033-129"
protocol-connection = "3.0.0.Beta1-20240411.165033-128"
raknet = "1.0.0.CR3-20240416.144209-1"
blockstateupdater="1.20.80-20240411.142413-1"
mcauthlib = "d9d773e"
mcprotocollib = "1.20.4-2-20240116.220521-7"
adventure = "4.14.0"
@ -38,9 +39,9 @@ mixin = "0.8.5"
# plugin versions
indra = "3.1.3"
shadow = "7.1.3-SNAPSHOT"
shadow = "8.1.1"
architectury-plugin = "3.4-SNAPSHOT"
architectury-loom = "1.4-SNAPSHOT"
architectury-loom = "1.6-SNAPSHOT"
minotaur = "2.8.7"
lombok = "8.4"
blossom = "1.2.0"
@ -75,6 +76,7 @@ netty-codec-haproxy = { group = "io.netty", name = "netty-codec-haproxy", versio
netty-handler = { group = "io.netty", name = "netty-handler", version.ref = "netty" }
netty-transport-native-epoll = { group = "io.netty", name = "netty-transport-native-epoll", version.ref = "netty" }
netty-transport-native-kqueue = { group = "io.netty", name = "netty-transport-native-kqueue", version.ref = "netty" }
netty-transport-native-io_uring = { group = "io.netty.incubator", name = "netty-incubator-transport-native-io_uring", version.ref = "netty-io-uring" }
log4j-api = { group = "org.apache.logging.log4j", name = "log4j-api", version.ref = "log4j" }
log4j-core = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4j" }
@ -113,9 +115,12 @@ viaproxy = { group = "net.raphimc", name = "ViaProxy", version.ref = "viaproxy"
viaversion = { group = "com.viaversion", name = "viaversion", version.ref = "viaversion" }
websocket = { group = "org.java-websocket", name = "Java-WebSocket", version.ref = "websocket" }
protocol-common = { group = "org.cloudburstmc.protocol", name = "common", version.ref = "protocol-connection" }
protocol-codec = { group = "org.cloudburstmc.protocol", name = "bedrock-codec", version.ref = "protocol" }
protocol-connection = { group = "org.cloudburstmc.protocol", name = "bedrock-connection", version.ref = "protocol-connection" }
#protocol-common = { group = "org.cloudburstmc.protocol", name = "common", version.ref = "protocol-connection" }
#protocol-codec = { group = "org.cloudburstmc.protocol", name = "bedrock-codec", version.ref = "protocol" }
#protocol-connection = { group = "org.cloudburstmc.protocol", name = "bedrock-connection", version.ref = "protocol-connection" }
protocol-common = { group = "com.github.GeyserMC.Protocol", name = "common", version = "ade21be" }
protocol-codec = { group = "com.github.GeyserMC.Protocol", name = "bedrock-codec", version = "ade21be" }
protocol-connection = { group = "com.github.GeyserMC.Protocol", name = "bedrock-connection", version = "ade21be" }
math = { group = "org.cloudburstmc.math", name = "immutable", version = "2.0" }

Binary file not shown.

View File

@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

35
gradlew vendored
View File

@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -80,13 +80,11 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -133,22 +131,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

21
gradlew.bat vendored
View File

@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@ -42,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail

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