mirror of https://github.com/GeyserMC/Geyser.git
Merge branch 'master' into master
This commit is contained in:
commit
5f070ddba4
|
@ -1,13 +1,27 @@
|
|||
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 Build Number
|
||||
run: |
|
||||
echo "BUILD_NUMBER=${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up JDK 17
|
||||
# See https://github.com/actions/setup-java/commits
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
|
@ -15,29 +29,12 @@ jobs:
|
|||
java-version: 17
|
||||
distribution: temurin
|
||||
|
||||
- name: Check if the author has forked the API repo
|
||||
# See https://github.com/Kas-tle/find-forks-action/commits
|
||||
uses: Kas-tle/find-forks-action@1b5447d1e3c7a8ed79583dd817cc5399686eed3a
|
||||
id: find_forks
|
||||
with:
|
||||
owner: GeyserMC
|
||||
repo: api
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Use author's API repo if it exists
|
||||
if: ${{ steps.find_forks.outputs.target_branch_found == 'true' }}
|
||||
env:
|
||||
API_FORK_URL: ${{ steps.find_forks.outputs.user_fork_url }}
|
||||
API_FORK_BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||
run: |
|
||||
git clone "${API_FORK_URL}" --single-branch --branch "${API_FORK_BRANCH}" api
|
||||
cd api
|
||||
./gradlew publishToMavenLocal
|
||||
|
||||
- name: Checkout repository and submodules
|
||||
# See https://github.com/actions/checkout/commits
|
||||
uses: actions/checkout@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
|
|
@ -7,7 +7,9 @@ on:
|
|||
- 'gh-readonly-queue/**'
|
||||
paths-ignore:
|
||||
- '.github/ISSUE_TEMPLATE/*.yml'
|
||||
- '.github/actions/pullrequest.yml'
|
||||
- '.github/actions/workflows/build-remote.yml'
|
||||
- '.github/actions/workflows/preview.yml'
|
||||
- '.github/actions/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_ENV
|
||||
|
||||
- name: Checkout repository and submodules
|
||||
# See https://github.com/actions/checkout/commits
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
@ -37,8 +48,8 @@ jobs:
|
|||
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
|
||||
|
|
|
@ -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/
|
|
@ -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 }}
|
|
@ -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.
|
||||
|
|
|
@ -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:.*"))
|
||||
|
|
|
@ -41,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:.*"))
|
||||
|
|
|
@ -12,7 +12,8 @@ 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:*")
|
||||
|
@ -57,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
|
||||
|
|
|
@ -22,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:.*"))
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ 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")
|
||||
|
@ -107,6 +109,7 @@ dependencies {
|
|||
}
|
||||
|
||||
repositories {
|
||||
// mavenLocal()
|
||||
maven("https://repo.opencollab.dev/maven-releases/")
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://jitpack.io")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -72,6 +72,9 @@ public class FireballEntity extends ThrowableEntity {
|
|||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (removedInVoid()) {
|
||||
return;
|
||||
}
|
||||
moveAbsoluteImmediate(tickMovement(position), getYaw(), getPitch(), getHeadYaw(), false, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
@ -74,7 +76,6 @@ 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_OFFLINE_PACKET_LIMIT;
|
||||
import static org.cloudburstmc.netty.channel.raknet.RakConstants.DEFAULT_PACKET_LIMIT;
|
||||
|
||||
public final class GeyserServer {
|
||||
|
@ -107,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.
|
||||
*/
|
||||
|
@ -124,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()
|
||||
|
@ -217,11 +225,6 @@ public final class GeyserServer {
|
|||
int rakPacketLimit = positivePropOrDefault("Geyser.RakPacketLimit", DEFAULT_PACKET_LIMIT);
|
||||
this.geyser.getLogger().debug("Setting RakNet packet limit to " + rakPacketLimit);
|
||||
|
||||
boolean isWhitelistedProxyProtocol = this.geyser.getConfig().getBedrock().isEnableProxyProtocol()
|
||||
&& !this.geyser.getConfig().getBedrock().getProxyProtocolWhitelistedIPs().isEmpty();
|
||||
int rakOfflinePacketLimit = positivePropOrDefault("Geyser.RakOfflinePacketLimit", isWhitelistedProxyProtocol ? Integer.MAX_VALUE : DEFAULT_OFFLINE_PACKET_LIMIT);
|
||||
this.geyser.getLogger().debug("Setting RakNet offline packet limit to " + rakOfflinePacketLimit);
|
||||
|
||||
int rakGlobalPacketLimit = positivePropOrDefault("Geyser.RakGlobalPacketLimit", DEFAULT_GLOBAL_PACKET_LIMIT);
|
||||
this.geyser.getLogger().debug("Setting RakNet global packet limit to " + rakGlobalPacketLimit);
|
||||
|
||||
|
@ -231,8 +234,8 @@ public final class GeyserServer {
|
|||
.option(RakChannelOption.RAK_HANDLE_PING, true)
|
||||
.option(RakChannelOption.RAK_MAX_MTU, this.geyser.getConfig().getMtu())
|
||||
.option(RakChannelOption.RAK_PACKET_LIMIT, rakPacketLimit)
|
||||
.option(RakChannelOption.RAK_OFFLINE_PACKET_LIMIT, rakOfflinePacketLimit)
|
||||
.option(RakChannelOption.RAK_GLOBAL_PACKET_LIMIT, rakGlobalPacketLimit)
|
||||
.option(RakChannelOption.RAK_SEND_COOKIE, true)
|
||||
.childHandler(serverInitializer);
|
||||
}
|
||||
|
||||
|
@ -248,6 +251,7 @@ public final class GeyserServer {
|
|||
}
|
||||
|
||||
if (!isWhitelistedIP) {
|
||||
connectionAttempts++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -270,10 +274,12 @@ public final class GeyserServer {
|
|||
geyser.eventBus().fire(requestEvent);
|
||||
if (requestEvent.isCancelled()) {
|
||||
geyser.getLogger().debug("Connection request from " + ip + " was cancelled using the API!");
|
||||
connectionAttempts++;
|
||||
return false;
|
||||
}
|
||||
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.attempt_connect", ip));
|
||||
geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.attempt_connect", ip));
|
||||
connectionAttempts++;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -414,22 +420,35 @@ public final class GeyserServer {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,7 +144,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 +229,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 +273,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 +371,7 @@ public final class BlockRegistryPopulator {
|
|||
.itemFrames(itemFrames)
|
||||
.flowerPotBlocks(flowerPotBlocks)
|
||||
.jigsawStates(jigsawDefinitions)
|
||||
.structureBlockStates(structureBlockDefinitions)
|
||||
.remappedVanillaIds(remappedVanillaIds)
|
||||
.blockProperties(customBlockProperties)
|
||||
.customBlockStateDefinitions(customBlockStateDefinitions)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,37 @@
|
|||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.protocol.bedrock;
|
||||
package org.geysermc.geyser.session.cache;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MapInfoRequestPacket;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@Setter
|
||||
@Getter
|
||||
public final class StructureBlockCache {
|
||||
|
||||
@Translator(packet = MapInfoRequestPacket.class)
|
||||
public class BedrockMapInfoRequestTranslator extends PacketTranslator<MapInfoRequestPacket> {
|
||||
/**
|
||||
* Stores the current structure's name to be able to detect changes in the loaded structure
|
||||
*/
|
||||
private @Nullable String currentStructureName;
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, MapInfoRequestPacket packet) {
|
||||
long mapId = packet.getUniqueMapId();
|
||||
/**
|
||||
* Stores the offset changes added by Geyser that ensure that structure bounds
|
||||
* are the same for Java and Bedrock
|
||||
*/
|
||||
private @Nullable Vector3i bedrockOffset;
|
||||
|
||||
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);
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -6,12 +6,13 @@ events = "1.1-SNAPSHOT"
|
|||
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-20240330.103819-16"
|
||||
raknet = "1.0.0.CR3-20240416.144209-1"
|
||||
blockstateupdater="1.20.70-20240303.125052-2"
|
||||
mcauthlib = "d9d773e"
|
||||
mcprotocollib = "1.20.4-2-20240116.220521-7"
|
||||
|
@ -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" }
|
||||
|
|
|
@ -4,6 +4,8 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
|||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
// mavenLocal()
|
||||
|
||||
// Floodgate, Cumulus etc.
|
||||
maven("https://repo.opencollab.dev/main")
|
||||
|
||||
|
@ -30,7 +32,6 @@ dependencyResolutionManagement {
|
|||
mavenContent { releasesOnly() }
|
||||
}
|
||||
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
|
||||
// ViaVersion
|
||||
|
|
Loading…
Reference in New Issue