Merge remote-tracking branch 'origin/master' into jwt-changes

# Conflicts:
#	core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java
#	core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java
#	core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java
This commit is contained in:
Tim203 2023-06-27 12:12:31 +02:00
commit 99671960d0
No known key found for this signature in database
GPG Key ID: 736F3CD49EF01DBF
141 changed files with 14840 additions and 41502 deletions

View File

@ -31,7 +31,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Use author's API repo if it exists
if: steps.find_forks.outputs.target_branch_found == 'true'
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 }}

View File

@ -14,7 +14,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
### Currently supporting Minecraft Bedrock 1.19.40 - 1.19.81 and Minecraft Java 1.19.4.
### Currently supporting Minecraft Bedrock 1.19.80 - 1.20 and Minecraft Java 1.20.
## Setting Up
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.

View File

@ -35,7 +35,9 @@ import org.geysermc.geyser.api.event.EventRegistrar;
import org.geysermc.geyser.api.extension.ExtensionManager;
import org.geysermc.geyser.api.network.BedrockListener;
import org.geysermc.geyser.api.network.RemoteServer;
import org.geysermc.geyser.api.util.PlatformType;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
@ -107,6 +109,30 @@ public interface GeyserApi extends GeyserApiBase {
@NonNull
BedrockListener bedrockListener();
/**
* Gets the {@link Path} to the Geyser config directory.
*
* @return the path to the Geyser config directory
*/
@NonNull
Path configDirectory();
/**
* Gets the {@link Path} to the Geyser packs directory.
*
* @return the path to the Geyser packs directory
*/
@NonNull
Path packDirectory();
/**
* Gets {@link PlatformType} the extension is running on
*
* @return type of platform
*/
@NonNull
PlatformType platformType();
/**
* Gets the current {@link GeyserApiBase} instance.
*

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.event.bedrock;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
/**
* Called when Geyser session connected to a Java remote server and is in a play-ready state.
*/
public final class SessionJoinEvent extends ConnectionEvent {
public SessionJoinEvent(@NonNull GeyserConnection connection) {
super(connection);
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.event.bedrock;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
import org.geysermc.geyser.api.pack.ResourcePack;
import java.util.List;
import java.util.UUID;
/**
* Called when Geyser initializes a session for a new Bedrock client and is in the process of sending resource packs.
*/
public abstract class SessionLoadResourcePacksEvent extends ConnectionEvent {
public SessionLoadResourcePacksEvent(@NonNull GeyserConnection connection) {
super(connection);
}
/**
* Gets an unmodifiable list of {@link ResourcePack}s that will be sent to the client.
*
* @return an unmodifiable list of resource packs that will be sent to the client.
*/
public abstract @NonNull List<ResourcePack> resourcePacks();
/**
* Registers a {@link ResourcePack} to be sent to the client.
*
* @param resourcePack a resource pack that will be sent to the client.
* @return true if the resource pack was added successfully,
* or false if already present
*/
public abstract boolean register(@NonNull ResourcePack resourcePack);
/**
* Unregisters a resource pack from being sent to the client.
*
* @param uuid the UUID of the resource pack
* @return true whether the resource pack was removed from the list of resource packs.
*/
public abstract boolean unregister(@NonNull UUID uuid);
}

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.event.bedrock;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.event.Cancellable;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
import org.geysermc.geyser.api.network.RemoteServer;
/**
* Called when a session has logged in, and is about to connect to a remote java server.
* This event is cancellable, and can be used to prevent the player from connecting to the remote server.
*/
public final class SessionLoginEvent extends ConnectionEvent implements Cancellable {
private RemoteServer remoteServer;
private boolean cancelled;
private String disconnectReason;
public SessionLoginEvent(@NonNull GeyserConnection connection, @NonNull RemoteServer remoteServer) {
super(connection);
this.remoteServer = remoteServer;
}
/**
* Returns whether the event is cancelled.
*
* @return The cancel status of the event.
*/
@Override
public boolean isCancelled() {
return this.cancelled;
}
/**
* Cancels the login event, and disconnects the player.
* If cancelled, the player disconnects without connecting to the remote server.
* This method will use a default disconnect reason. To specify one, use {@link #setCancelled(boolean, String)}.
*
* @param cancelled If the login event should be cancelled.
*/
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
/**
* Cancels the login event, and disconnects the player with the specified reason.
* If cancelled, the player disconnects without connecting to the remote server.
*
* @param cancelled If the login event should be cancelled.
* @param disconnectReason The reason for the cancellation.
*/
public void setCancelled(boolean cancelled, @NonNull String disconnectReason) {
this.cancelled = cancelled;
this.disconnectReason = disconnectReason;
}
/**
* Returns the reason for the cancellation, or null if there is no reason given.
*
* @return The reason for the cancellation.
*/
public @Nullable String disconnectReason() {
return this.disconnectReason;
}
/**
* Gets the {@link RemoteServer} the section will attempt to connect to.
*
* @return the {@link RemoteServer} the section will attempt to connect to.
*/
public @NonNull RemoteServer remoteServer() {
return this.remoteServer;
}
/**
* Sets the {@link RemoteServer} to connect the session to.
*
* @param remoteServer Sets the {@link RemoteServer} to connect to.
*/
public void remoteServer(@NonNull RemoteServer remoteServer) {
this.remoteServer = remoteServer;
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.pack;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.GeyserApi;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Path;
/**
* Represents a pack codec that can be used
* to provide resource packs to clients.
*/
public abstract class PackCodec {
/**
* Gets the sha256 hash of the resource pack.
*
* @return the hash of the resource pack
*/
public abstract byte @NonNull [] sha256();
/**
* Gets the resource pack size.
*
* @return the resource pack file size
*/
public abstract long size();
/**
* Serializes the given resource pack into a byte buffer.
*
* @param resourcePack the resource pack to serialize
* @return the serialized resource pack
*/
@NonNull
public abstract SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException;
/**
* Creates a new resource pack from this codec.
*
* @return the new resource pack
*/
@NonNull
protected abstract ResourcePack create();
/**
* Creates a new pack provider from the given path.
*
* @param path the path to create the pack provider from
* @return the new pack provider
*/
@NonNull
public static PackCodec path(@NonNull Path path) {
return GeyserApi.api().provider(PathPackCodec.class, path);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -23,21 +23,23 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.common;
package org.geysermc.geyser.api.pack;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
@Getter
@AllArgsConstructor
public enum PlatformType {
ANDROID("Android"),
BUNGEECORD("BungeeCord"),
FABRIC("Fabric"),
SPIGOT("Spigot"),
SPONGE("Sponge"),
STANDALONE("Standalone"),
VELOCITY("Velocity");
import java.nio.file.Path;
private final String platformName;
/**
* Represents a pack codec that creates a resource
* pack from a path on the filesystem.
*/
public abstract class PathPackCodec extends PackCodec {
/**
* Gets the path of the resource pack.
*
* @return the path of the resource pack
*/
@NonNull
public abstract Path path();
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.pack;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Represents a resource pack sent to Bedrock clients
* <p>
* This representation of a resource pack only contains what
* Geyser requires to send it to the client.
*/
public interface ResourcePack {
/**
* The {@link PackCodec codec} for this pack.
*
* @return the codec for this pack
*/
@NonNull
PackCodec codec();
/**
* Gets the resource pack manifest.
*
* @return the resource pack manifest
*/
@NonNull
ResourcePackManifest manifest();
/**
* Gets the content key of the resource pack. Lack of a content key is represented by an empty String.
*
* @return the content key of the resource pack
*/
@NonNull
String contentKey();
/**
* Creates a resource pack with the given {@link PackCodec}.
*
* @param codec the pack codec
* @return the resource pack
*/
@NonNull
static ResourcePack create(@NonNull PackCodec codec) {
return codec.create();
}
}

View File

@ -0,0 +1,209 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.pack;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;
import java.util.UUID;
/**
* Represents a resource pack manifest.
*/
public interface ResourcePackManifest {
/**
* Gets the format version of the resource pack.
*
* @return the format version
*/
int formatVersion();
/**
* Gets the header of the resource pack.
*
* @return the header
*/
@NonNull
Header header();
/**
* Gets the modules of the resource pack.
*
* @return the modules
*/
@NonNull
Collection<? extends Module> modules();
/**
* Gets the dependencies of the resource pack.
*
* @return the dependencies
*/
@NonNull
Collection<? extends Dependency> dependencies();
/**
* Represents the header of a resource pack.
*/
interface Header {
/**
* Gets the UUID of the resource pack.
*
* @return the UUID
*/
@NonNull
UUID uuid();
/**
* Gets the version of the resource pack.
*
* @return the version
*/
@NonNull
Version version();
/**
* Gets the name of the resource pack.
*
* @return the name
*/
@NonNull
String name();
/**
* Gets the description of the resource pack.
*
* @return the description
*/
@NonNull
String description();
/**
* Gets the minimum supported Minecraft version of the resource pack.
*
* @return the minimum supported Minecraft version
*/
@NonNull
Version minimumSupportedMinecraftVersion();
}
/**
* Represents a module of a resource pack.
*/
interface Module {
/**
* Gets the UUID of the module.
*
* @return the UUID
*/
@NonNull
UUID uuid();
/**
* Gets the version of the module.
*
* @return the version
*/
@NonNull
Version version();
/**
* Gets the type of the module.
*
* @return the type
*/
@NonNull
String type();
/**
* Gets the description of the module.
*
* @return the description
*/
@NonNull
String description();
}
/**
* Represents a dependency of a resource pack.
*/
interface Dependency {
/**
* Gets the UUID of the dependency.
*
* @return the uuid
*/
@NonNull
UUID uuid();
/**
* Gets the version of the dependency.
*
* @return the version
*/
@NonNull
Version version();
}
/**
* Represents a version of a resource pack.
*/
interface Version {
/**
* Gets the major version.
*
* @return the major version
*/
int major();
/**
* Gets the minor version.
*
* @return the minor version
*/
int minor();
/**
* Gets the patch version.
*
* @return the patch version
*/
int patch();
/**
* Gets the version formatted as a String.
*
* @return the version string
*/
@NonNull String toString();
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.util;
/**
* Represents the platform Geyser is running on.
*/
public record PlatformType(String platformName) {
public static final PlatformType ANDROID = new PlatformType("Android");
public static final PlatformType BUNGEECORD = new PlatformType("BungeeCord");
public static final PlatformType FABRIC = new PlatformType("Fabric");
public static final PlatformType SPIGOT = new PlatformType("Spigot");
public static final PlatformType SPONGE = new PlatformType("Sponge");
public static final PlatformType STANDALONE = new PlatformType("Standalone");
public static final PlatformType VELOCITY = new PlatformType("Velocity");
}

View File

@ -8,6 +8,7 @@ platformRelocate("net.md_5.bungee.jni")
platformRelocate("com.fasterxml.jackson")
platformRelocate("io.netty.channel.kqueue") // This is not used because relocating breaks natives, but we must include it or else we get ClassDefNotFound
platformRelocate("net.kyori")
platformRelocate("org.yaml") // Broken as of 1.20
// These dependencies are already present on the platform
provided(libs.bungeecord.proxy)
@ -21,7 +22,6 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
dependencies {
exclude(dependency("com.google.*:.*"))
exclude(dependency("org.yaml:.*"))
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
exclude(dependency("io.netty:netty-handler:.*"))
@ -32,4 +32,4 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
exclude(dependency("io.netty:netty-codec:.*"))
exclude(dependency("io.netty:netty-resolver-dns:.*"))
}
}
}

View File

@ -31,7 +31,7 @@ import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.protocol.ProtocolConstants;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;

View File

@ -1,3 +1,5 @@
import net.fabricmc.loom.task.RemapJarTask
plugins {
id("fabric-loom") version "1.0-SNAPSHOT"
id("com.modrinth.minotaur") version "2.+"
@ -89,8 +91,16 @@ tasks {
dependsOn(shadowJar)
inputFile.set(shadowJar.get().archiveFile)
archiveBaseName.set("Geyser-Fabric")
archiveClassifier.set("")
archiveVersion.set("")
archiveClassifier.set("")
}
register("remapModrinthJar", RemapJarTask::class) {
dependsOn(shadowJar)
inputFile.set(shadowJar.get().archiveFile)
archiveBaseName.set("geyser-fabric")
archiveVersion.set(project.version.toString() + "+build." + System.getenv("GITHUB_RUN_NUMBER"))
archiveClassifier.set("")
}
}
@ -103,12 +113,13 @@ modrinth {
syncBodyFrom.set(rootProject.file("README.md").readText())
uploadFile.set(tasks.getByPath("remapJar"))
gameVersions.addAll("1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4")
uploadFile.set(tasks.getByPath("remapModrinthJar"))
gameVersions.addAll("1.20")
loaders.add("fabric")
failSilently.set(true)
dependencies {
required.project("fabric-api")
}
}
}

View File

@ -36,7 +36,7 @@ import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.server.MinecraftServer;
import org.apache.logging.log4j.LogManager;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
@ -217,10 +217,12 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
return this.server.getServerVersion();
}
@SuppressWarnings("ConstantConditions") // IDEA thinks that ip cannot be null
@NotNull
@Override
public String getServerBindAddress() {
return this.server.getLocalIp();
String ip = this.server.getLocalIp();
return ip != null ? ip : ""; // See issue #3812
}
@Override

View File

@ -72,7 +72,7 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
return;
}
LevelChunk chunk = player.getLevel().getChunk(x, z);
LevelChunk chunk = player.level().getChunk(x, z);
final int chunkBlockX = x << 4;
final int chunkBlockZ = z << 4;
for (int i = 0; i < blockEntityInfos.size(); i++) {
@ -92,7 +92,7 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
return;
}
BlockEntity blockEntity = player.level.getBlockEntity(new BlockPos(x, y, z));
BlockEntity blockEntity = player.level().getBlockEntity(new BlockPos(x, y, z));
sendLecternData(session, blockEntity, false);
});
}
@ -166,7 +166,7 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
BlockPos pos = new BlockPos(x, y, z);
// Don't create a new block entity if invalid
BlockEntity blockEntity = player.level.getChunkAt(pos).getBlockEntity(pos);
BlockEntity blockEntity = player.level().getChunkAt(pos).getBlockEntity(pos);
if (blockEntity instanceof BannerBlockEntity banner) {
// Potentially exposes other NBT data? But we need to get the NBT data for the banner patterns *and*
// the banner might have a custom name, both of which a Java client knows and caches

View File

@ -23,9 +23,9 @@
"geyser-fabric.mixins.json"
],
"depends": {
"fabricloader": ">=0.14.8",
"fabricloader": ">=0.14.21",
"fabric": "*",
"minecraft": ">=1.19",
"minecraft": ">=1.20",
"fabric-permissions-api-v0": "*"
}
}

View File

@ -29,6 +29,7 @@ platformRelocate("com.fasterxml.jackson")
platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger")
platformRelocate("org.objectweb.asm")
platformRelocate("me.lucko.commodore")
platformRelocate("org.yaml") // Broken as of 1.20
// These dependencies are already present on the platform
provided(libs.viaversion)
@ -42,7 +43,6 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
dependencies {
exclude(dependency("com.google.*:.*"))
exclude(dependency("org.yaml:.*"))
// We cannot shade Netty, or else native libraries will not load
// Needed because older Spigot builds do not provide the haproxy module
@ -64,4 +64,4 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
// Commodore includes Brigadier
exclude(dependency("com.mojang:.*"))
}
}
}

View File

@ -42,7 +42,7 @@ import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;

View File

@ -27,7 +27,7 @@ package org.geysermc.geyser.platform.sponge;
import com.google.inject.Inject;
import org.apache.logging.log4j.Logger;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;

View File

@ -7,10 +7,8 @@ dependencies {
api(projects.core)
implementation(libs.terminalconsoleappender) {
exclude("org.apache.logging.log4j", "log4j-core")
exclude("org.jline", "jline-reader")
exclude("org.jline", "jline-terminal")
exclude("org.jline", "jline-terminal-jna")
exclude("org.apache.logging.log4j")
exclude("org.jline")
}
implementation(libs.bundles.jline)

View File

@ -38,7 +38,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandManager;
@ -181,14 +181,14 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
}
}
this.geyserLogger = new GeyserStandaloneLogger();
if (useGui && gui == null) {
gui = new GeyserStandaloneGUI();
gui = new GeyserStandaloneGUI(geyserLogger);
gui.redirectSystemStreams();
gui.startUpdateThread();
}
geyserLogger = new GeyserStandaloneLogger();
LoopbackUtil.checkAndApplyLoopback(geyserLogger);
try {
@ -224,7 +224,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
geyserCommandManager.init();
if (gui != null) {
gui.setupInterface(geyserLogger, geyserCommandManager);
gui.enableCommands(geyser.getScheduledThread(), geyserCommandManager);
}
geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);

View File

@ -92,6 +92,7 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
Configurator.setLevel(log.getName(), debug ? Level.DEBUG : Level.INFO);
}
@Override
public boolean isDebug() {
return log.isDebugEnabled();
}

View File

@ -86,7 +86,7 @@ public class ColorPane extends JTextPane {
while (stillSearching) {
mIndex = addString.indexOf("m", aPos); // find the end of the escape sequence
if (mIndex < 0) { // the buffer ends halfway through the ansi string!
remaining = addString.substring(aPos, addString.length());
remaining = addString.substring(aPos);
stillSearching = false;
continue;
} else {
@ -99,7 +99,7 @@ public class ColorPane extends JTextPane {
aIndex = addString.indexOf("\u001B", aPos);
if (aIndex == -1) { // if that was the last sequence of the input, send remaining text
tmpString = addString.substring(aPos, addString.length());
tmpString = addString.substring(aPos);
append(colorCurrent, tmpString);
stillSearching = false;
continue; // jump out of loop early, as the whole string has been sent now

View File

@ -26,10 +26,8 @@
package org.geysermc.geyser.platform.standalone.gui;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.platform.standalone.GeyserStandaloneLogger;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
@ -45,28 +43,37 @@ import java.io.PrintStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class GeyserStandaloneGUI {
private static final DefaultTableModel playerTableModel = new DefaultTableModel();
private static final List<Integer> ramValues = new ArrayList<>();
private static final ColorPane consolePane = new ColorPane();
private static final GraphPanel ramGraph = new GraphPanel();
private static final JTable playerTable = new JTable(playerTableModel);
private static final int originalFontSize = consolePane.getFont().getSize();
private static final long MEGABYTE = 1024L * 1024L;
private final JMenu commandsMenu;
private final JMenu optionsMenu;
private final GeyserLogger logger;
private final ColorPane consolePane = new ColorPane();
private final int originalFontSize = consolePane.getFont().getSize();
private final JTextField commandInput = new JTextField();
private final CommandListener commandListener = new CommandListener();
private final GraphPanel ramGraph = new GraphPanel();
private final List<Integer> ramValues = new ArrayList<>();
private final DefaultTableModel playerTableModel = new DefaultTableModel();
private final JTable playerTable = new JTable(playerTableModel);
/**
* Create and show the Geyser-Standalone GUI
*
* @param logger the logger for determining debug mode, and executing commands from the console
*/
public GeyserStandaloneGUI(GeyserLogger logger) {
this.logger = logger;
public GeyserStandaloneGUI() {
// Create the frame and setup basic settings
JFrame frame = new JFrame(GeyserLocale.getLocaleStringLog("geyser.gui.title"));
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
@ -81,8 +88,7 @@ public class GeyserStandaloneGUI {
// Show a confirm dialog on close
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent we)
{
public void windowClosing(WindowEvent we) {
String[] buttons = {GeyserLocale.getLocaleStringLog("geyser.gui.exit.confirm"), GeyserLocale.getLocaleStringLog("geyser.gui.exit.deny")};
int result = JOptionPane.showOptionDialog(frame, GeyserLocale.getLocaleStringLog("geyser.gui.exit.message"), frame.getTitle(), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, buttons, buttons[1]);
if (result == JOptionPane.YES_OPTION) {
@ -100,91 +106,41 @@ public class GeyserStandaloneGUI {
frame.setIconImage(icon.getImage());
}
// File, View, Options, etc
setupMenuBar(frame);
// Setup the split pane and event listeners
JSplitPane splitPane = new JSplitPane();
splitPane.setDividerLocation(600);
splitPane.addPropertyChangeListener("dividerLocation", e -> splitPaneLimit((JSplitPane)e.getSource()));
splitPane.addPropertyChangeListener("dividerLocation", e -> splitPaneLimit((JSplitPane) e.getSource()));
splitPane.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
splitPaneLimit((JSplitPane)e.getSource());
splitPaneLimit((JSplitPane) e.getSource());
}
});
cp.add(splitPane, BorderLayout.CENTER);
// Set the background and disable input for the text pane
// Holds console and command input components
JPanel leftPane = new JPanel(new BorderLayout());
splitPane.setLeftComponent(leftPane);
// Set the background and disable editing of the console
consolePane.setBackground(Color.BLACK);
consolePane.setEditable(false);
// Wrap the text pane in a scroll pane and add it to the form
JScrollPane consoleScrollPane = new JScrollPane(consolePane);
//cp.add(consoleScrollPane, BorderLayout.CENTER);
splitPane.setLeftComponent(consoleScrollPane);
leftPane.add(consoleScrollPane, BorderLayout.CENTER);
// Create a new menu bar for the top of the frame
JMenuBar menuBar = new JMenuBar();
// Create 'File'
JMenu fileMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file"));
fileMenu.setMnemonic(KeyEvent.VK_F);
menuBar.add(fileMenu);
// 'Open Geyser folder' button
JMenuItem openButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file.open_folder"), KeyEvent.VK_O);
openButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK));
openButton.addActionListener(e -> {
try {
Desktop.getDesktop().open(new File("./"));
} catch (IOException ignored) { }
});
fileMenu.add(openButton);
fileMenu.addSeparator();
// 'Exit' button
JMenuItem exitButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file.exit"), KeyEvent.VK_X);
exitButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_MASK));
exitButton.addActionListener(e -> System.exit(0));
fileMenu.add(exitButton);
// Create 'Commands'
commandsMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.commands"));
commandsMenu.setMnemonic(KeyEvent.VK_C);
menuBar.add(commandsMenu);
// Create 'View'
JMenu viewMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view"));
viewMenu.setMnemonic(KeyEvent.VK_V);
menuBar.add(viewMenu);
// 'Zoom in' button
JMenuItem zoomInButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.zoom_in"));
zoomInButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, InputEvent.CTRL_DOWN_MASK));
zoomInButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() + 1)));
viewMenu.add(zoomInButton);
// 'Zoom in' button
JMenuItem zoomOutButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.zoom_out"));
zoomOutButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK));
zoomOutButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() - 1)));
viewMenu.add(zoomOutButton);
// 'Reset Zoom' button
JMenuItem resetZoomButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.reset_zoom"));
resetZoomButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), originalFontSize)));
viewMenu.add(resetZoomButton);
// create 'Options'
optionsMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.options"));
viewMenu.setMnemonic(KeyEvent.VK_O);
menuBar.add(optionsMenu);
// Set the frames menu bar
frame.setJMenuBar(menuBar);
// a bit taller than the default layout - width is ignored fortunately
commandInput.setPreferredSize(new Dimension(100, 25));
commandInput.setEnabled(false); // disabled until command handler is initialized
commandInput.addActionListener(commandListener);
leftPane.add(commandInput, BorderLayout.SOUTH);
JPanel rightPane = new JPanel();
rightPane.setLayout(new CardLayout(5, 5));
//cp.add(rightPane, BorderLayout.EAST);
splitPane.setRightComponent(rightPane);
JPanel rightContentPane = new JPanel();
@ -209,12 +165,75 @@ public class GeyserStandaloneGUI {
frame.setVisible(true);
}
private void setupMenuBar(JFrame frame) {
// Create a new menu bar for the top of the frame
JMenuBar menuBar = new JMenuBar();
// Create 'File'
JMenu fileMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file"));
fileMenu.setMnemonic(KeyEvent.VK_F);
menuBar.add(fileMenu);
// 'Open Geyser folder' button
JMenuItem openButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file.open_folder"), KeyEvent.VK_O);
openButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK));
openButton.addActionListener(e -> {
try {
Desktop.getDesktop().open(new File("./"));
} catch (IOException ignored) { }
});
fileMenu.add(openButton);
fileMenu.addSeparator();
// 'Exit' button
JMenuItem exitButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file.exit"), KeyEvent.VK_X);
exitButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_DOWN_MASK));
exitButton.addActionListener(e -> System.exit(0));
fileMenu.add(exitButton);
// Create 'View'
JMenu viewMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view"));
viewMenu.setMnemonic(KeyEvent.VK_V);
menuBar.add(viewMenu);
// 'Zoom in' button
JMenuItem zoomInButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.zoom_in"));
zoomInButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, InputEvent.CTRL_DOWN_MASK));
zoomInButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() + 1)));
viewMenu.add(zoomInButton);
// 'Zoom in' button
JMenuItem zoomOutButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.zoom_out"));
zoomOutButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK));
zoomOutButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() - 1)));
viewMenu.add(zoomOutButton);
// 'Reset Zoom' button
JMenuItem resetZoomButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.reset_zoom"));
resetZoomButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), originalFontSize)));
viewMenu.add(resetZoomButton);
// create 'Options'
JMenu optionsMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.options"));
viewMenu.setMnemonic(KeyEvent.VK_O);
menuBar.add(optionsMenu);
JCheckBoxMenuItem debugMode = new JCheckBoxMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.options.toggle_debug_mode"));
debugMode.setSelected(logger.isDebug());
debugMode.addActionListener(e -> logger.setDebug(debugMode.getState()));
optionsMenu.add(debugMode);
// Set the frames menu bar
frame.setJMenuBar(menuBar);
}
/**
* Queue up an update to the text pane so we don't block the main thread
*
* @param text The text to append
*/
private void updateTextPane(final String text) {
private void appendConsole(final String text) {
SwingUtilities.invokeLater(() -> {
consolePane.appendANSI(text);
Document doc = consolePane.getDocument();
@ -230,12 +249,12 @@ public class GeyserStandaloneGUI {
OutputStream out = new OutputStream() {
@Override
public void write(final int b) {
updateTextPane(String.valueOf((char) b));
appendConsole(String.valueOf((char) b));
}
@Override
public void write(byte[] b, int off, int len) {
updateTextPane(new String(b, off, len));
appendConsole(new String(b, off, len));
}
@Override
@ -251,50 +270,17 @@ public class GeyserStandaloneGUI {
}
/**
* Add all the Geyser commands to the commands menu, and setup the debug mode toggle
* Enable the command input box.
*
* @param geyserStandaloneLogger The current logger
* @param geyserCommandManager The commands manager
* @param executor the executor for running commands off the GUI thread
* @param commandManager the command manager to delegate commands to
*/
public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, GeyserCommandManager geyserCommandManager) {
commandsMenu.removeAll();
optionsMenu.removeAll();
for (Map.Entry<String, Command> entry : geyserCommandManager.getCommands().entrySet()) {
// Remove the offhand command and any alias commands to prevent duplicates in the list
if (!entry.getValue().isExecutableOnConsole() || entry.getValue().aliases().contains(entry.getKey())) {
continue;
}
GeyserCommand command = (GeyserCommand) entry.getValue();
// Create the button that runs the command
boolean hasSubCommands = !entry.getValue().subCommands().isEmpty();
// Add an extra menu if there are more commands that can be run
JMenuItem commandButton = hasSubCommands ? new JMenu(entry.getValue().name()) : new JMenuItem(entry.getValue().name());
commandButton.getAccessibleContext().setAccessibleDescription(entry.getValue().description());
if (!hasSubCommands) {
commandButton.addActionListener(e -> command.execute(null, geyserStandaloneLogger, new String[]{ }));
} else {
// Add a submenu that's the same name as the menu can't be pressed
JMenuItem otherCommandButton = new JMenuItem(entry.getValue().name());
otherCommandButton.getAccessibleContext().setAccessibleDescription(entry.getValue().description());
otherCommandButton.addActionListener(e -> command.execute(null, geyserStandaloneLogger, new String[]{ }));
commandButton.add(otherCommandButton);
// Add a menu option for all possible subcommands
for (String subCommandName : entry.getValue().subCommands()) {
JMenuItem item = new JMenuItem(subCommandName);
item.addActionListener(e -> command.execute(null, geyserStandaloneLogger, new String[]{subCommandName}));
commandButton.add(item);
}
}
commandsMenu.add(commandButton);
}
// 'Debug Mode' toggle
JCheckBoxMenuItem debugMode = new JCheckBoxMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.options.toggle_debug_mode"));
debugMode.setSelected(geyserStandaloneLogger.isDebug());
debugMode.addActionListener(e -> geyserStandaloneLogger.setDebug(!geyserStandaloneLogger.isDebug()));
optionsMenu.add(debugMode);
public void enableCommands(ScheduledExecutorService executor, GeyserCommandManager commandManager) {
// we don't want to block the GUI thread with the command execution
// todo: once cloud is used, an AsynchronousCommandExecutionCoordinator can be used to avoid this scheduler
commandListener.handler = cmd -> executor.schedule(() -> commandManager.runCommand(logger, cmd), 0, TimeUnit.SECONDS);
commandInput.setEnabled(true);
commandInput.requestFocusInWindow();
}
/**
@ -322,14 +308,14 @@ public class GeyserStandaloneGUI {
// Update ram graph
final long freeMemory = Runtime.getRuntime().freeMemory();
final long totalMemory = Runtime.getRuntime().totalMemory();
final int freePercent = (int)(freeMemory * 100.0 / totalMemory + 0.5);
final int freePercent = (int) (freeMemory * 100.0 / totalMemory + 0.5);
ramValues.add(100 - freePercent);
ramGraph.setXLabel(GeyserLocale.getLocaleStringLog("geyser.gui.graph.usage", String.format("%,d", (totalMemory - freeMemory) / MEGABYTE), freePercent));
// Trim the list
int k = ramValues.size();
if ( k > 10 )
if (k > 10)
ramValues.subList(0, k - 10).clear();
// Update the graph
@ -354,4 +340,17 @@ public class GeyserStandaloneGUI {
splitPane.setDividerLocation(frame.getWidth() - 200);
}
}
private class CommandListener implements ActionListener {
private Consumer<String> handler;
@Override
public void actionPerformed(ActionEvent e) {
String command = commandInput.getText();
appendConsole(command + "\n"); // show what was run in the console
handler.accept(command); // run the command
commandInput.setText(""); // clear the input
}
}
}

View File

@ -2,10 +2,10 @@
<Configuration status="WARN">
<Appenders>
<TerminalConsole name="TerminalConsole">
<PatternLayout pattern="[%d{HH:mm:ss} %style{%highlight{%level}{FATAL=red dark, ERROR=red, WARN=yellow bright, INFO=cyan bright, DEBUG=green, TRACE=white}}] %minecraftFormatting{%msg}%n"/>
<PatternLayout pattern="[%d{HH:mm:ss} %style{%highlight{%level}{FATAL=red, ERROR=red, WARN=yellow bright, INFO=cyan bright, DEBUG=green, TRACE=white}}] %minecraftFormatting{%msg}%n"/>
</TerminalConsole>
<Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="[%d{HH:mm:ss} %style{%highlight{%level}{FATAL=red dark, ERROR=red, WARN=yellow bright, INFO=cyan bright, DEBUG=green, TRACE=white}}] %minecraftFormatting{%msg}%n"/>
<PatternLayout pattern="[%d{HH:mm:ss} %style{%highlight{%level}{FATAL=red, ERROR=red, WARN=yellow bright, INFO=cyan bright, DEBUG=green, TRACE=white}}] %minecraftFormatting{%msg}%n"/>
</Console>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%d{HH:mm:ss.SSS} %t/%level] %minecraftFormatting{%msg}{strip}%n"/>

View File

@ -36,7 +36,7 @@ import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.proxy.ProxyServer;
import lombok.Getter;
import net.kyori.adventure.util.Codec;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;

View File

@ -6,7 +6,7 @@ plugins {
allprojects {
group = "org.geysermc.geyser"
version = "2.1.0-SNAPSHOT"
version = "2.1.1-SNAPSHOT"
description = "Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers."
tasks.withType<JavaCompile> {

View File

@ -30,7 +30,7 @@ dependencies {
}
implementation(libs.raknet) {
exclude("io.netty", "*");
exclude("io.netty", "*")
}
implementation(libs.netty.resolver.dns)

View File

@ -26,7 +26,7 @@
package org.geysermc.connector;
import org.geysermc.api.Geyser;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.geyser.GeyserImpl;

View File

@ -43,7 +43,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.Geyser;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.util.FormBuilder;
import org.geysermc.erosion.packet.Packets;
@ -69,9 +69,9 @@ import org.geysermc.geyser.event.GeyserEventBus;
import org.geysermc.geyser.extension.GeyserExtensionManager;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.network.netty.GeyserServer;
import org.geysermc.geyser.pack.ResourcePack;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.loader.RegistryLoaders;
import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
@ -90,6 +90,7 @@ import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.security.Key;
import java.text.DecimalFormat;
import java.util.*;
@ -217,25 +218,9 @@ public class GeyserImpl implements GeyserApi {
GeyserConfiguration config = bootstrap.getGeyserConfig();
boolean isGui = false;
// This will check if we are in standalone and get the 'useGui' variable from there
if (platformType == PlatformType.STANDALONE) {
try {
Class<?> cls = Class.forName("org.geysermc.geyser.platform.standalone.GeyserStandaloneBootstrap");
isGui = (boolean) cls.getMethod("isUseGui").invoke(cls.cast(bootstrap));
} catch (Exception e) {
logger.debug("Failed detecting if standalone is using a GUI; if this is a GeyserConnect instance this can be safely ignored.");
}
}
double completeTime = (System.currentTimeMillis() - startupTime) / 1000D;
String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime)) + " ";
if (isGui) {
message += GeyserLocale.getLocaleStringLog("geyser.core.finish.gui");
} else {
message += GeyserLocale.getLocaleStringLog("geyser.core.finish.console");
}
String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime));
message += " " + GeyserLocale.getLocaleStringLog("geyser.core.finish.console");
logger.info(message);
if (platformType == PlatformType.STANDALONE) {
@ -258,7 +243,7 @@ public class GeyserImpl implements GeyserApi {
SkinProvider.registerCacheImageTask(this);
ResourcePack.loadPacks();
Registries.RESOURCE_PACKS.load();
String geyserUdpPort = System.getProperty("geyserUdpPort", "");
String pluginUdpPort = geyserUdpPort.isEmpty() ? System.getProperty("pluginUdpPort", "") : geyserUdpPort;
@ -410,7 +395,7 @@ public class GeyserImpl implements GeyserApi {
metrics.addCustomChart(new Metrics.SingleLineChart("players", sessionManager::size));
// Prevent unwanted words best we can
metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().authType().toString().toLowerCase(Locale.ROOT)));
metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName));
metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::platformName));
metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", GeyserLocale::getDefaultLocale));
metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserImpl.VERSION));
metrics.addCustomChart(new Metrics.AdvancedPie("playerPlatform", () -> {
@ -446,7 +431,7 @@ public class GeyserImpl implements GeyserApi {
if (minecraftVersion != null) {
Map<String, Map<String, Integer>> versionMap = new HashMap<>();
Map<String, Integer> platformMap = new HashMap<>();
platformMap.put(platformType.getPlatformName(), 1);
platformMap.put(platformType.platformName(), 1);
versionMap.put(minecraftVersion, platformMap);
metrics.addCustomChart(new Metrics.DrilldownPie("minecraftServerVersion", () -> {
@ -622,7 +607,7 @@ public class GeyserImpl implements GeyserApi {
this.erosionUnixListener.close();
}
ResourcePack.PACKS.clear();
Registries.RESOURCE_PACKS.get().clear();
this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus));
this.extensionManager.disableExtensions();
@ -681,6 +666,24 @@ public class GeyserImpl implements GeyserApi {
return getConfig().getBedrock();
}
@Override
@NonNull
public Path configDirectory() {
return bootstrap.getConfigFolder();
}
@Override
@NonNull
public Path packDirectory() {
return bootstrap.getConfigFolder().resolve("packs");
}
@Override
@NonNull
public PlatformType platformType() {
return platformType;
}
public int buildNumber() {
if (!this.isProductionEnvironment()) {
return 0;

View File

@ -29,7 +29,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.command.CommandExecutor;

View File

@ -26,7 +26,7 @@
package org.geysermc.geyser.command.defaults;
import com.fasterxml.jackson.databind.JsonNode;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;

View File

@ -30,7 +30,7 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;

View File

@ -25,7 +25,7 @@
package org.geysermc.geyser.command.defaults;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.command.GeyserCommand;

View File

@ -25,7 +25,7 @@
package org.geysermc.geyser.command.defaults;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;

View File

@ -25,7 +25,7 @@
package org.geysermc.geyser.command.defaults;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;

View File

@ -26,7 +26,7 @@
package org.geysermc.geyser.command.defaults;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;

View File

@ -27,7 +27,7 @@ package org.geysermc.geyser.dump;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.text.AsteriskSerializer;

View File

@ -104,6 +104,7 @@ public final class EntityDefinitions {
public static final EntityDefinition<HorseEntity> HORSE;
public static final EntityDefinition<ZombieEntity> HUSK;
public static final EntityDefinition<SpellcasterIllagerEntity> ILLUSIONER; // Not present on Bedrock
public static final EntityDefinition<InteractionEntity> INTERACTION;
public static final EntityDefinition<IronGolemEntity> IRON_GOLEM;
public static final EntityDefinition<ItemEntity> ITEM;
public static final EntityDefinition<ItemFrameEntity> ITEM_FRAME;
@ -133,6 +134,7 @@ public final class EntityDefinitions {
public static final EntityDefinition<AbstractFishEntity> SALMON;
public static final EntityDefinition<SheepEntity> SHEEP;
public static final EntityDefinition<ShulkerEntity> SHULKER;
public static final EntityDefinition<SnifferEntity> SNIFFER;
public static final EntityDefinition<ThrowableEntity> SHULKER_BULLET;
public static final EntityDefinition<MonsterEntity> SILVERFISH;
public static final EntityDefinition<SkeletonEntity> SKELETON;
@ -235,7 +237,7 @@ public final class EntityDefinitions {
.type(EntityType.EXPERIENCE_ORB)
.identifier("minecraft:xp_orb")
.build();
EVOKER_FANGS = EntityDefinition.builder(EvokerFangsEntity::new) // No entity metadata to listen to as of 1.18.1
EVOKER_FANGS = EntityDefinition.inherited(EvokerFangsEntity::new, entityBase)
.type(EntityType.EVOKER_FANGS)
.height(0.8f).width(0.5f)
.identifier("minecraft:evocation_fang")
@ -318,6 +320,15 @@ public final class EntityDefinitions {
.addTranslator(MetadataType.CHAT, TextDisplayEntity::setText)
.build();
INTERACTION = EntityDefinition.inherited(InteractionEntity::new, entityBase)
.type(EntityType.INTERACTION)
.heightAndWidth(1.0f) // default size until server specifies otherwise
.identifier("minecraft:armor_stand")
.addTranslator(MetadataType.FLOAT, InteractionEntity::setWidth)
.addTranslator(MetadataType.FLOAT, InteractionEntity::setHeight)
.addTranslator(MetadataType.BOOLEAN, InteractionEntity::setResponse)
.build();
EntityDefinition<FireballEntity> fireballBase = EntityDefinition.inherited(FireballEntity::new, entityBase)
.addTranslator(null) // Item
.build();
@ -842,6 +853,12 @@ public final class EntityDefinitions {
.height(1.3f).width(0.9f)
.addTranslator(MetadataType.BYTE, SheepEntity::setSheepFlags)
.build();
SNIFFER = EntityDefinition.inherited(SnifferEntity::new, ageableEntityBase)
.type(EntityType.SNIFFER)
.height(1.75f).width(1.9f)
.addTranslator(MetadataType.SNIFFER_STATE, SnifferEntity::setSnifferState)
.addTranslator(null) // Integer, drop seed at tick
.build();
STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase)
.type(EntityType.STRIDER)
.height(1.7f).width(0.9f)
@ -884,7 +901,6 @@ public final class EntityDefinitions {
.build();
CAMEL = EntityDefinition.inherited(CamelEntity::new, abstractHorseEntityBase)
.type(EntityType.CAMEL)
.identifier("minecraft:llama") // todo 1.20
.height(2.375f).width(1.7f)
.addTranslator(MetadataType.BOOLEAN, CamelEntity::setDashing)
.addTranslator(null) // Last pose change tick

View File

@ -125,8 +125,8 @@ public class BoatEntity extends Entity {
public void setVariant(IntEntityMetadata entityMetadata) {
variant = entityMetadata.getPrimitiveValue();
dirtyMetadata.put(EntityDataTypes.VARIANT, switch (variant) {
case 6, 7 -> variant - 1; // Dark oak and mangrove
case 5, 8 -> 0; // TODO temp until 1.20. Cherry and bamboo
case 6, 7, 8 -> variant - 1; // dark_oak, mangrove, bamboo
case 5 -> 8; // cherry
default -> variant;
});
}

View File

@ -493,9 +493,10 @@ public class Entity implements GeyserEntity {
* Update the mount offsets of each passenger on this vehicle
*/
protected void updatePassengerOffsets() {
for (Entity passenger : passengers) {
for (int i = 0; i < passengers.size(); i++) {
Entity passenger = passengers.get(i);
if (passenger != null) {
boolean rider = passengers.get(0) == this;
boolean rider = i == 0;
EntityUtils.updateMountOffset(passenger, this, rider, true, passengers.size() > 1);
passenger.updateBedrockMetadata();
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.entity.type;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import java.util.UUID;
public class InteractionEntity extends Entity {
/**
* true - java client hears swing sound when attacking, and arm swings when right-clicking
* false - java client hears no swing sound when attacking, and arm does not swing when right-clicking
*/
private boolean response = false;
public InteractionEntity(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);
}
@Override
protected void initializeMetadata() {
super.initializeMetadata();
// hide the armor stand but keep the hitbox active
setFlag(EntityFlag.INVISIBLE, true);
}
@Override
public InteractionResult interact(Hand hand) {
// these InteractionResults do mirror the java client
// but the bedrock client won't arm swing itself because of our armor stand workaround
if (response) {
AnimatePacket animatePacket = new AnimatePacket();
animatePacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
session.sendUpstreamPacket(animatePacket);
session.sendDownstreamPacket(new ServerboundSwingPacket(hand));
return InteractionResult.SUCCESS;
}
return InteractionResult.CONSUME;
}
public void setWidth(FloatEntityMetadata width) {
setBoundingBoxWidth(width.getPrimitiveValue());
}
public void setHeight(FloatEntityMetadata height) {
setBoundingBoxHeight(height.getPrimitiveValue());
}
public void setResponse(BooleanEntityMetadata response) {
this.response = response.getPrimitiveValue();
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.entity.type.living.animal;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.SnifferState;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.Tickable;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import java.util.UUID;
public class SnifferEntity extends AnimalEntity implements Tickable {
private static final float DIGGING_HEIGHT = EntityDefinitions.SNIFFER.height() - 0.4f;
private static final int DIG_END = 120;
private static final int DIG_START = DIG_END - 34;
private Pose pose = Pose.STANDING; // Needed to call setDimensions for DIGGING state
private int digTicks;
public SnifferEntity(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);
}
@Override
public void setPose(Pose pose) {
this.pose = pose;
super.setPose(pose);
}
@Override
protected void setDimensions(Pose pose) {
if (getFlag(EntityFlag.DIGGING)) {
setBoundingBoxHeight(DIGGING_HEIGHT);
setBoundingBoxWidth(definition.width());
} else {
super.setDimensions(pose);
}
}
@Override
public boolean canEat(Item item) {
return session.getTagCache().isSnifferFood(item);
}
public void setSnifferState(ObjectEntityMetadata<SnifferState> entityMetadata) {
SnifferState snifferState = entityMetadata.getValue();
// SnifferState.SCENTING and SnifferState.IDLING not used in bedrock
// The bedrock client does the scenting animation and sound on its own
setFlag(EntityFlag.FEELING_HAPPY, snifferState == SnifferState.FEELING_HAPPY);
setFlag(EntityFlag.SCENTING, snifferState == SnifferState.SNIFFING); // SnifferState.SNIFFING -> EntityFlag.SCENTING
setFlag(EntityFlag.SEARCHING, snifferState == SnifferState.SEARCHING);
setFlag(EntityFlag.DIGGING, snifferState == SnifferState.DIGGING);
setFlag(EntityFlag.RISING, snifferState == SnifferState.RISING);
setDimensions(pose);
if (getFlag(EntityFlag.DIGGING)) {
digTicks = DIG_END;
} else {
// Handles situations where the DIGGING state is exited earlier than expected,
// such as hitting the sniffer or joining the game while it is digging
digTicks = 0;
}
}
@Override
public void tick() {
// The java client renders digging particles on its own, but bedrock does not
if (digTicks > 0 && --digTicks < DIG_START && digTicks % 5 == 0) {
Vector3f rot = Vector3f.createDirectionDeg(0, -getYaw()).mul(2.25f);
Vector3f pos = getPosition().add(rot).up(0.2f).floor(); // Handle non-full blocks
int blockId = session.getBlockMappings().getBedrockBlockId(session.getGeyser().getWorldManager().getBlockAt(session, pos.toInt().down()));
LevelEventPacket levelEventPacket = new LevelEventPacket();
levelEventPacket.setType(LevelEvent.PARTICLE_DESTROY_BLOCK_NO_SOUND);
levelEventPacket.setPosition(pos);
levelEventPacket.setData(blockId);
session.sendUpstreamPacket(levelEventPacket);
if (digTicks % 10 == 0) {
LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket();
levelSoundEventPacket.setSound(SoundEvent.HIT);
levelSoundEventPacket.setPosition(pos);
levelSoundEventPacket.setExtraData(blockId);
levelSoundEventPacket.setIdentifier(":");
session.sendUpstreamPacket(levelSoundEventPacket);
}
}
}
}

View File

@ -27,8 +27,13 @@ package org.geysermc.geyser.entity.type.living.animal.horse;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item;
@ -38,16 +43,50 @@ import java.util.UUID;
public class CamelEntity extends AbstractHorseEntity {
private static final float SITTING_HEIGHT_DIFFERENCE = 1.43F;
public static final float SITTING_HEIGHT_DIFFERENCE = 1.43F;
public CamelEntity(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);
dirtyMetadata.put(EntityDataTypes.CONTAINER_TYPE, (byte) ContainerType.HORSE.getId());
// Always tamed, but not indicated in horse flags
setFlag(EntityFlag.TAMED, true);
}
@Override
protected void initializeMetadata() {
super.initializeMetadata();
this.dirtyMetadata.put(EntityDataTypes.VARIANT, 2); // Closest llama colour to camel
public void setHorseFlags(ByteEntityMetadata entityMetadata) {
byte xd = entityMetadata.getPrimitiveValue();
boolean saddled = (xd & 0x04) == 0x04;
setFlag(EntityFlag.SADDLED, saddled);
setFlag(EntityFlag.EATING, (xd & 0x10) == 0x10);
setFlag(EntityFlag.STANDING, (xd & 0x20) == 0x20);
// HorseFlags
// Bred 0x10
// Eating 0x20
// Open mouth 0x80
int horseFlags = 0x0;
horseFlags = (xd & 0x40) == 0x40 ? horseFlags | 0x80 : horseFlags;
// Only set eating when we don't have mouth open so a player interaction doesn't trigger the eating animation
horseFlags = (xd & 0x10) == 0x10 && (xd & 0x40) != 0x40 ? horseFlags | 0x20 : horseFlags;
// Set the flags into the horse flags
dirtyMetadata.put(EntityDataTypes.HORSE_FLAGS, horseFlags);
// Send the eating particles
// We use the wheat metadata as static particles since Java
// doesn't send over what item was used to feed the horse
if ((xd & 0x40) == 0x40) {
EntityEventPacket entityEventPacket = new EntityEventPacket();
entityEventPacket.setRuntimeEntityId(geyserId);
entityEventPacket.setType(EntityEventType.EATING_ITEM);
entityEventPacket.setData(session.getItemMappings().getStoredItems().wheat().getBedrockDefinition().getRuntimeId() << 16);
session.sendUpstreamPacket(entityEventPacket);
}
// Shows the dash meter
setFlag(EntityFlag.CAN_DASH, saddled);
}
@Override
@ -55,10 +94,16 @@ public class CamelEntity extends AbstractHorseEntity {
return item == Items.CACTUS;
}
@Override
public void setPose(Pose pose) {
setFlag(EntityFlag.SITTING, pose == Pose.SITTING);
super.setPose(pose);
}
@Override
protected void setDimensions(Pose pose) {
if (pose == Pose.SITTING) {
setBoundingBoxWidth(definition.height() - SITTING_HEIGHT_DIFFERENCE);
setBoundingBoxHeight(definition.height() - SITTING_HEIGHT_DIFFERENCE);
setBoundingBoxWidth(definition.width());
} else {
super.setDimensions(pose);

View File

@ -67,10 +67,6 @@ public class SessionPlayerEntity extends PlayerEntity {
*/
@Getter
private boolean isRidingInFront;
/**
* Used for villager inventory emulation.
*/
private int fakeTradeXp;
public SessionPlayerEntity(GeyserSession session) {
super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null);
@ -175,11 +171,6 @@ public class SessionPlayerEntity extends PlayerEntity {
this.isRidingInFront = position != null && position.getX() > 0;
}
public void addFakeTradeExperience(int tradeXp) {
fakeTradeXp += tradeXp;
dirtyMetadata.put(EntityDataTypes.TRADE_EXPERIENCE, fakeTradeXp);
}
@Override
public AttributeData createHealthAttribute() {
// Max health must be divisible by two in bedrock

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.event.type;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.event.bedrock.SessionLoadResourcePacksEvent;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.session.GeyserSession;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent {
private final Map<String, ResourcePack> packs;
public SessionLoadResourcePacksEventImpl(GeyserSession session, Map<String, ResourcePack> packMap) {
super(session);
this.packs = packMap;
}
public @NonNull Map<String, ResourcePack> getPacks() {
return packs;
}
@Override
public @NonNull List<ResourcePack> resourcePacks() {
return List.copyOf(packs.values());
}
@Override
public boolean register(@NonNull ResourcePack resourcePack) {
String packID = resourcePack.manifest().header().uuid().toString();
if (packs.containsValue(resourcePack) || packs.containsKey(packID)) {
return false;
}
packs.put(resourcePack.manifest().header().uuid().toString(), resourcePack);
return true;
}
@Override
public boolean unregister(@NonNull UUID uuid) {
return packs.remove(uuid.toString()) != null;
}
}

View File

@ -27,6 +27,7 @@ package org.geysermc.geyser.extension.event;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.Event;
import org.geysermc.event.FireResult;
import org.geysermc.event.PostOrder;
import org.geysermc.event.subscribe.Subscriber;
import org.geysermc.geyser.api.event.EventBus;
@ -47,10 +48,15 @@ public record GeyserExtensionEventBus(EventBus<EventRegistrar> eventBus, Extensi
}
@Override
public boolean fire(@NonNull Event event) {
public FireResult fire(@NonNull Event event) {
return eventBus.fire(event);
}
@Override
public FireResult fireSilently(@NonNull Event event) {
return eventBus.fireSilently(event);
}
@Override
public @NonNull <T extends Event> Set<? extends EventSubscriber<EventRegistrar, T>> subscribers(@NonNull Class<T> eventClass) {
return eventBus.subscribers(eventClass);

View File

@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.inventory.VillagerTrade;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundMerchantOffersPacket;
import lombok.Getter;
import lombok.Setter;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.session.GeyserSession;
@ -40,6 +41,8 @@ public class MerchantContainer extends Container {
private VillagerTrade[] villagerTrades;
@Getter @Setter
private ClientboundMerchantOffersPacket pendingOffersPacket;
@Getter @Setter
private int tradeExperience;
public MerchantContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
super(title, id, size, containerType, playerInventory);
@ -49,9 +52,10 @@ public class MerchantContainer extends Container {
if (villagerTrades != null && slot >= 0 && slot < villagerTrades.length) {
VillagerTrade trade = villagerTrades[slot];
setItem(2, GeyserItemStack.from(trade.getOutput()), session);
// TODO this logic doesn't add up
session.getPlayerEntity().addFakeTradeExperience(trade.getXp());
session.getPlayerEntity().updateBedrockMetadata();
tradeExperience += trade.getXp();
villager.getDirtyMetadata().put(EntityDataTypes.TRADE_EXPERIENCE, tradeExperience);
villager.updateBedrockMetadata();
}
}
}

View File

@ -51,6 +51,7 @@ public class StoredItemMappings {
private final ItemMapping egg;
private final ItemMapping shield;
private final ItemMapping wheat;
private final ItemMapping writableBook;
public StoredItemMappings(Map<Item, ItemMapping> itemMappings) {
this.bamboo = load(itemMappings, Items.BAMBOO);
@ -64,6 +65,7 @@ public class StoredItemMappings {
this.egg = load(itemMappings, Items.EGG);
this.shield = load(itemMappings, Items.SHIELD);
this.wheat = load(itemMappings, Items.WHEAT);
this.writableBook = load(itemMappings, Items.WRITABLE_BOOK);
}
@Nonnull

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.inventory.recipe;
import org.cloudburstmc.protocol.bedrock.data.TrimMaterial;
import org.cloudburstmc.protocol.bedrock.data.TrimPattern;
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemTagDescriptor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Hardcoded recipe information about armor trims until further improvements can be made. This information was scraped
* from BDS 1.19.81 with a world with the next_major_update and sniffer features enabled, using ProxyPass.
*/
public class TrimRecipe {
// For TrimDataPacket, which BDS sends just before the CraftingDataPacket
public static final List<TrimPattern> PATTERNS;
public static final List<TrimMaterial> MATERIALS;
// For CraftingDataPacket
public static final String ID = "minecraft:smithing_armor_trim";
public static final ItemDescriptorWithCount BASE = tagDescriptor("minecraft:trimmable_armors");
public static final ItemDescriptorWithCount ADDITION = tagDescriptor("minecraft:trim_materials");
public static final ItemDescriptorWithCount TEMPLATE = tagDescriptor("minecraft:trim_templates");
static {
List<TrimPattern> patterns = new ArrayList<>(16);
patterns.add(new TrimPattern("minecraft:ward_armor_trim_smithing_template", "ward"));
patterns.add(new TrimPattern("minecraft:sentry_armor_trim_smithing_template", "sentry"));
patterns.add(new TrimPattern("minecraft:snout_armor_trim_smithing_template", "snout"));
patterns.add(new TrimPattern("minecraft:dune_armor_trim_smithing_template", "dune"));
patterns.add(new TrimPattern("minecraft:spire_armor_trim_smithing_template", "spire"));
patterns.add(new TrimPattern("minecraft:tide_armor_trim_smithing_template", "tide"));
patterns.add(new TrimPattern("minecraft:wild_armor_trim_smithing_template", "wild"));
patterns.add(new TrimPattern("minecraft:rib_armor_trim_smithing_template", "rib"));
patterns.add(new TrimPattern("minecraft:coast_armor_trim_smithing_template", "coast"));
patterns.add(new TrimPattern("minecraft:shaper_armor_trim_smithing_template", "shaper"));
patterns.add(new TrimPattern("minecraft:eye_armor_trim_smithing_template", "eye"));
patterns.add(new TrimPattern("minecraft:vex_armor_trim_smithing_template", "vex"));
patterns.add(new TrimPattern("minecraft:silence_armor_trim_smithing_template", "silence"));
patterns.add(new TrimPattern("minecraft:wayfinder_armor_trim_smithing_template", "wayfinder"));
patterns.add(new TrimPattern("minecraft:raiser_armor_trim_smithing_template", "raiser"));
patterns.add(new TrimPattern("minecraft:host_armor_trim_smithing_template", "host"));
PATTERNS = Collections.unmodifiableList(patterns);
List<TrimMaterial> materials = new ArrayList<>(10);
materials.add(new TrimMaterial("quartz", "§h", "minecraft:quartz"));
materials.add(new TrimMaterial("iron", "§i", "minecraft:iron_ingot"));
materials.add(new TrimMaterial("netherite", "§j", "minecraft:netherite_ingot"));
materials.add(new TrimMaterial("redstone", "§m", "minecraft:redstone"));
materials.add(new TrimMaterial("copper", "§n", "minecraft:copper_ingot"));
materials.add(new TrimMaterial("gold", "§p", "minecraft:gold_ingot"));
materials.add(new TrimMaterial("emerald", "§q", "minecraft:emerald"));
materials.add(new TrimMaterial("diamond", "§s", "minecraft:diamond"));
materials.add(new TrimMaterial("lapis", "§t", "minecraft:lapis_lazuli"));
materials.add(new TrimMaterial("amethyst", "§u", "minecraft:amethyst_shard"));
MATERIALS = Collections.unmodifiableList(materials);
}
private TrimRecipe() {
//no-op
}
private static ItemDescriptorWithCount tagDescriptor(String tag) {
return new ItemDescriptorWithCount(new ItemTagDescriptor(tag), 1);
}
}

View File

@ -84,6 +84,7 @@ public final class Items {
public static final Item BEDROCK = register(new BlockItem("bedrock", builder()));
public static final Item SAND = register(new BlockItem("sand", builder()));
public static final Item SUSPICIOUS_SAND = register(new BlockItem("suspicious_sand", builder()));
public static final Item SUSPICIOUS_GRAVEL = register(new BlockItem("suspicious_gravel", builder()));
public static final Item RED_SAND = register(new BlockItem("red_sand", builder()));
public static final Item GRAVEL = register(new BlockItem("gravel", builder()));
public static final Item COAL_ORE = register(new BlockItem("coal_ore", builder()));
@ -247,6 +248,7 @@ public final class Items {
public static final Item LILY_OF_THE_VALLEY = register(new FlowerItem("lily_of_the_valley", builder()));
public static final Item WITHER_ROSE = register(new FlowerItem("wither_rose", builder()));
public static final Item TORCHFLOWER = register(new FlowerItem("torchflower", builder()));
public static final Item PITCHER_PLANT = register(new BlockItem("pitcher_plant", builder()));
public static final Item SPORE_BLOSSOM = register(new BlockItem("spore_blossom", builder()));
public static final Item BROWN_MUSHROOM = register(new BlockItem("brown_mushroom", builder()));
public static final Item RED_MUSHROOM = register(new BlockItem("red_mushroom", builder()));
@ -302,7 +304,7 @@ public final class Items {
public static final Item BRICKS = register(new BlockItem("bricks", builder()));
public static final Item BOOKSHELF = register(new BlockItem("bookshelf", builder()));
public static final Item CHISELED_BOOKSHELF = register(new BlockItem("chiseled_bookshelf", builder()));
public static final Item DECORATED_POT = register(new BlockItem("decorated_pot", builder().stackSize(1)));
public static final Item DECORATED_POT = register(new DecoratedPotItem("decorated_pot", builder().stackSize(1)));
public static final Item MOSSY_COBBLESTONE = register(new BlockItem("mossy_cobblestone", builder()));
public static final Item OBSIDIAN = register(new BlockItem("obsidian", builder()));
public static final Item TORCH = register(new BlockItem("torch", builder()));
@ -313,7 +315,7 @@ public final class Items {
public static final Item PURPUR_PILLAR = register(new BlockItem("purpur_pillar", builder()));
public static final Item PURPUR_STAIRS = register(new BlockItem("purpur_stairs", builder()));
public static final Item SPAWNER = register(new BlockItem("spawner", builder()));
public static final Item CHEST = register(new BlockItem("chest", builder()));
public static final Item CHEST = register(new ChestItem("chest", builder()));
public static final Item CRAFTING_TABLE = register(new BlockItem("crafting_table", builder()));
public static final Item FARMLAND = register(new BlockItem("farmland", builder()));
public static final Item FURNACE = register(new BlockItem("furnace", builder()));
@ -395,7 +397,7 @@ public final class Items {
public static final Item END_STONE_BRICKS = register(new BlockItem("end_stone_bricks", builder()));
public static final Item DRAGON_EGG = register(new BlockItem("dragon_egg", builder()));
public static final Item SANDSTONE_STAIRS = register(new BlockItem("sandstone_stairs", builder()));
public static final Item ENDER_CHEST = register(new BlockItem("ender_chest", builder()));
public static final Item ENDER_CHEST = register(new ChestItem("ender_chest", builder()));
public static final Item EMERALD_BLOCK = register(new BlockItem("emerald_block", builder()));
public static final Item OAK_STAIRS = register(new BlockItem("oak_stairs", builder()));
public static final Item SPRUCE_STAIRS = register(new BlockItem("spruce_stairs", builder()));
@ -602,6 +604,7 @@ public final class Items {
public static final Item RED_CONCRETE_POWDER = register(new BlockItem("red_concrete_powder", builder()));
public static final Item BLACK_CONCRETE_POWDER = register(new BlockItem("black_concrete_powder", builder()));
public static final Item TURTLE_EGG = register(new BlockItem("turtle_egg", builder()));
public static final Item SNIFFER_EGG = register(new BlockItem("sniffer_egg", builder()));
public static final Item DEAD_TUBE_CORAL_BLOCK = register(new BlockItem("dead_tube_coral_block", builder()));
public static final Item DEAD_BRAIN_CORAL_BLOCK = register(new BlockItem("dead_brain_coral_block", builder()));
public static final Item DEAD_BUBBLE_CORAL_BLOCK = register(new BlockItem("dead_bubble_coral_block", builder()));
@ -689,8 +692,9 @@ public final class Items {
public static final Item LIGHTNING_ROD = register(new BlockItem("lightning_rod", builder()));
public static final Item DAYLIGHT_DETECTOR = register(new BlockItem("daylight_detector", builder()));
public static final Item SCULK_SENSOR = register(new BlockItem("sculk_sensor", builder()));
public static final Item CALIBRATED_SCULK_SENSOR = register(new BlockItem("calibrated_sculk_sensor", builder()));
public static final Item TRIPWIRE_HOOK = register(new BlockItem("tripwire_hook", builder()));
public static final Item TRAPPED_CHEST = register(new BlockItem("trapped_chest", builder()));
public static final Item TRAPPED_CHEST = register(new ChestItem("trapped_chest", builder()));
public static final Item TNT = register(new BlockItem("tnt", builder()));
public static final Item REDSTONE_LAMP = register(new BlockItem("redstone_lamp", builder()));
public static final Item NOTE_BLOCK = register(new BlockItem("note_block", builder()));
@ -1080,8 +1084,8 @@ public final class Items {
public static final Item ZOMBIFIED_PIGLIN_SPAWN_EGG = register(new SpawnEggItem("zombified_piglin_spawn_egg", builder()));
public static final Item EXPERIENCE_BOTTLE = register(new Item("experience_bottle", builder()));
public static final Item FIRE_CHARGE = register(new Item("fire_charge", builder()));
public static final Item WRITABLE_BOOK = register(new ReadableBookItem("writable_book", builder().stackSize(1)));
public static final Item WRITTEN_BOOK = register(new ReadableBookItem("written_book", builder().stackSize(16)));
public static final Item WRITABLE_BOOK = register(new WritableBookItem("writable_book", builder().stackSize(1)));
public static final Item WRITTEN_BOOK = register(new WrittenBookItem("written_book", builder().stackSize(16)));
public static final Item ITEM_FRAME = register(new Item("item_frame", builder()));
public static final Item GLOW_ITEM_FRAME = register(new Item("glow_item_frame", builder()));
public static final Item FLOWER_POT = register(new BlockItem("flower_pot", builder()));
@ -1141,6 +1145,7 @@ public final class Items {
public static final Item CHORUS_FRUIT = register(new Item("chorus_fruit", builder()));
public static final Item POPPED_CHORUS_FRUIT = register(new Item("popped_chorus_fruit", builder()));
public static final Item TORCHFLOWER_SEEDS = register(new BlockItem("torchflower_seeds", builder()));
public static final Item PITCHER_POD = register(new BlockItem("pitcher_pod", builder()));
public static final Item BEETROOT = register(new Item("beetroot", builder()));
public static final Item BEETROOT_SEEDS = register(new BlockItem("beetroot_seeds", builder()));
public static final Item BEETROOT_SOUP = register(new Item("beetroot_soup", builder().stackSize(1)));
@ -1168,6 +1173,7 @@ public final class Items {
public static final Item MUSIC_DISC_11 = register(new Item("music_disc_11", builder().stackSize(1)));
public static final Item MUSIC_DISC_WAIT = register(new Item("music_disc_wait", builder().stackSize(1)));
public static final Item MUSIC_DISC_OTHERSIDE = register(new Item("music_disc_otherside", builder().stackSize(1)));
public static final Item MUSIC_DISC_RELIC = register(new Item("music_disc_relic", builder().stackSize(1)));
public static final Item MUSIC_DISC_5 = register(new Item("music_disc_5", builder().stackSize(1)));
public static final Item MUSIC_DISC_PIGSTEP = register(new Item("music_disc_pigstep", builder().stackSize(1)));
public static final Item DISC_FRAGMENT_5 = register(new Item("disc_fragment_5", builder()));
@ -1186,7 +1192,7 @@ public final class Items {
public static final Item PIGLIN_BANNER_PATTERN = register(new Item("piglin_banner_pattern", builder().stackSize(1)));
public static final Item GOAT_HORN = register(new GoatHornItem("goat_horn", builder().stackSize(1)));
public static final Item COMPOSTER = register(new BlockItem("composter", builder()));
public static final Item BARREL = register(new BlockItem("barrel", builder()));
public static final Item BARREL = register(new ChestItem("barrel", builder()));
public static final Item SMOKER = register(new BlockItem("smoker", builder()));
public static final Item BLAST_FURNACE = register(new BlockItem("blast_furnace", builder()));
public static final Item CARTOGRAPHY_TABLE = register(new BlockItem("cartography_table", builder()));
@ -1262,10 +1268,31 @@ public final class Items {
public static final Item SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("snout_armor_trim_smithing_template", builder()));
public static final Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("rib_armor_trim_smithing_template", builder()));
public static final Item SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("spire_armor_trim_smithing_template", builder()));
public static final Item POTTERY_SHARD_ARCHER = register(new Item("pottery_shard_archer", builder()));
public static final Item POTTERY_SHARD_PRIZE = register(new Item("pottery_shard_prize", builder()));
public static final Item POTTERY_SHARD_ARMS_UP = register(new Item("pottery_shard_arms_up", builder()));
public static final Item POTTERY_SHARD_SKULL = register(new Item("pottery_shard_skull", builder()));
public static final Item WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("wayfinder_armor_trim_smithing_template", builder()));
public static final Item SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("shaper_armor_trim_smithing_template", builder()));
public static final Item SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("silence_armor_trim_smithing_template", builder()));
public static final Item RAISER_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("raiser_armor_trim_smithing_template", builder()));
public static final Item HOST_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("host_armor_trim_smithing_template", builder()));
public static final Item ANGLER_POTTERY_SHERD = register(new Item("angler_pottery_sherd", builder()));
public static final Item ARCHER_POTTERY_SHERD = register(new Item("archer_pottery_sherd", builder()));
public static final Item ARMS_UP_POTTERY_SHERD = register(new Item("arms_up_pottery_sherd", builder()));
public static final Item BLADE_POTTERY_SHERD = register(new Item("blade_pottery_sherd", builder()));
public static final Item BREWER_POTTERY_SHERD = register(new Item("brewer_pottery_sherd", builder()));
public static final Item BURN_POTTERY_SHERD = register(new Item("burn_pottery_sherd", builder()));
public static final Item DANGER_POTTERY_SHERD = register(new Item("danger_pottery_sherd", builder()));
public static final Item EXPLORER_POTTERY_SHERD = register(new Item("explorer_pottery_sherd", builder()));
public static final Item FRIEND_POTTERY_SHERD = register(new Item("friend_pottery_sherd", builder()));
public static final Item HEART_POTTERY_SHERD = register(new Item("heart_pottery_sherd", builder()));
public static final Item HEARTBREAK_POTTERY_SHERD = register(new Item("heartbreak_pottery_sherd", builder()));
public static final Item HOWL_POTTERY_SHERD = register(new Item("howl_pottery_sherd", builder()));
public static final Item MINER_POTTERY_SHERD = register(new Item("miner_pottery_sherd", builder()));
public static final Item MOURNER_POTTERY_SHERD = register(new Item("mourner_pottery_sherd", builder()));
public static final Item PLENTY_POTTERY_SHERD = register(new Item("plenty_pottery_sherd", builder()));
public static final Item PRIZE_POTTERY_SHERD = register(new Item("prize_pottery_sherd", builder()));
public static final Item SHEAF_POTTERY_SHERD = register(new Item("sheaf_pottery_sherd", builder()));
public static final Item SHELTER_POTTERY_SHERD = register(new Item("shelter_pottery_sherd", builder()));
public static final Item SKULL_POTTERY_SHERD = register(new Item("skull_pottery_sherd", builder()));
public static final Item SNORT_POTTERY_SHERD = register(new Item("snort_pottery_sherd", builder()));
private static <T extends Item> T register(T item) {
return register(item, Registries.JAVA_ITEMS.get().size());

View File

@ -25,7 +25,12 @@
package org.geysermc.geyser.item.type;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.item.ArmorMaterial;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
public class ArmorItem extends Item {
private final ArmorMaterial material;
@ -35,8 +40,42 @@ public class ArmorItem extends Item {
this.material = material;
}
@Override
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
super.translateNbtToBedrock(session, tag);
if (tag.get("Trim") instanceof CompoundTag trim) {
StringTag material = trim.remove("material");
StringTag pattern = trim.remove("pattern");
// bedrock has an uppercase first letter key, and the value is not namespaced
trim.put(new StringTag("Material", stripNamespace(material.getValue())));
trim.put(new StringTag("Pattern", stripNamespace(pattern.getValue())));
}
}
@Override
public void translateNbtToJava(@NonNull CompoundTag tag, @NonNull ItemMapping mapping) {
super.translateNbtToJava(tag, mapping);
if (tag.get("Trim") instanceof CompoundTag trim) {
StringTag material = trim.remove("Material");
StringTag pattern = trim.remove("Pattern");
// java has a lowercase key, and namespaced value
trim.put(new StringTag("material", "minecraft:" + material.getValue()));
trim.put(new StringTag("pattern", "minecraft:" + pattern.getValue()));
}
}
@Override
public boolean isValidRepairItem(Item other) {
return material.getRepairIngredient() == other;
}
private static String stripNamespace(String identifier) {
int i = identifier.indexOf(':');
if (i >= 0) {
return identifier.substring(i + 1);
}
return identifier;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -23,42 +23,31 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.translator.inventory.item;
package org.geysermc.geyser.item.type;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.session.GeyserSession;
public abstract class NbtItemStackTranslator {
/**
* Translate the item NBT to Bedrock
* @param session the client's current session
* @param itemTag the item's CompoundTag (cloned from Geyser's cached copy)
* @param mapping Geyser's item mapping
*/
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) {
public class ChestItem extends BlockItem {
public ChestItem(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
/**
* Translate the item NBT to Java.
* @param itemTag the item's CompoundTag
* @param mapping Geyser's item mapping
*/
public void translateToJava(CompoundTag itemTag, ItemMapping mapping) {
@Override
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
super.translateNbtToBedrock(session, tag);
// Strip the BlockEntityTag from the chests contents
// sent to the client. The client does not parse this
// or use it for anything, as this tag is fully
// server-side, so we remove it to reduce bandwidth and
// solve potential issues with very large tags.
// There was a problem in the past where this would strip
// NBT data in creative mode, however with the new server
// authoritative inventories, this is no longer a concern.
tag.remove("BlockEntityTag");
}
/**
* Gets whether this nbt translator takes in this item.
*
* @param item Geyser's item mapping
* @return if the item should be processed under this class
*/
public boolean acceptItem(Item item) {
return true;
} // TODO
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.item.type;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.session.GeyserSession;
public class DecoratedPotItem extends BlockItem {
public DecoratedPotItem(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
super.translateNbtToBedrock(session, tag);
if (tag.remove("BlockEntityTag") instanceof CompoundTag blockEntityTag) {
if (blockEntityTag.remove("sherds") instanceof ListTag sherds) {
// bedrock wants it on the root level
tag.put(sherds);
}
}
}
}

View File

@ -144,7 +144,7 @@ public class Item {
}
/**
* Takes NBT from Java Edition and converts any value that Bedrock parses differently. <br>
* Takes NBT from Bedrock Edition and converts any value that Java parses differently. <br>
* Do note that this method is, these days, only called in three places (as of 2023/~1.19):
* <ul>
* <li>Extra recipe loading</li>

View File

@ -25,13 +25,40 @@
package org.geysermc.geyser.item.type;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.item.components.ToolTier;
import org.geysermc.geyser.session.GeyserSession;
public class ShieldItem extends Item {
public ShieldItem(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
super.translateNbtToBedrock(session, tag);
if (tag.remove("BlockEntityTag") instanceof CompoundTag blockEntityTag) {
if (blockEntityTag.get("Patterns") instanceof ListTag patterns) {
for (Tag pattern : patterns) {
if (((CompoundTag) pattern).get("Color") instanceof IntTag color) {
color.setValue(15 - color.getValue());
}
}
// Bedrock looks for patterns at the root
tag.put(patterns);
}
if (blockEntityTag.get("Base") instanceof IntTag base) {
base.setValue(15 - base.getValue());
tag.put(base);
}
}
}
@Override
public boolean isValidRepairItem(Item other) {
// Java Edition 1.19.3 checks the tag, but TODO check to see if we want it or are simulating what Bedrock is doing

View File

@ -77,9 +77,17 @@ public class ShulkerBoxItem extends BlockItem {
itemsList.add(boxItemTag);
}
tag.put(itemsList);
// Don't actually bother with removing the block entity tag. Too risky to translate
// if the user is on creative and messing with a shulker box
//itemTag.remove("BlockEntityTag");
// Strip the BlockEntityTag from the chests contents
// sent to the client. The client does not parse this
// or use it for anything, as this tag is fully
// server-side, so we remove it to reduce bandwidth and
// solve potential issues with very large tags.
// There was a problem in the past where this would strip
// NBT data in creative mode, however with the new server
// authoritative inventories, this is no longer a concern.
tag.remove("BlockEntityTag");
}
@Override

View File

@ -37,11 +37,8 @@ import org.geysermc.geyser.translator.text.MessageTranslator;
import java.util.ArrayList;
import java.util.List;
/**
* Encapsulates written books and writable books. Customly named class to share common code.
*/
public class ReadableBookItem extends Item {
public ReadableBookItem(String javaIdentifier, Builder builder) {
public class WritableBookItem extends Item {
public WritableBookItem(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.item.type;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.text.MessageTranslator;
import java.util.List;
public class WrittenBookItem extends WritableBookItem {
public static final int MAXIMUM_PAGE_EDIT_LENGTH = 1024;
public static final int MAXIMUM_PAGE_LENGTH = 32768;
public static final int MAXIMUM_PAGE_COUNT = 100; // Java edition limit. Bedrock edition has a limit of 50 pages.
public static final int MAXIMUM_TITLE_LENGTH = 16;
public WrittenBookItem(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
boolean isValid = isValidWrittenBook(tag);
if (!isValid) {
tag.remove("pages");
}
super.translateNbtToBedrock(session, tag);
if (!isValid) {
CompoundTag invalidTagPage = new CompoundTag("");
invalidTagPage.put(new StringTag("photoname", ""));
invalidTagPage.put(new StringTag(
"text",
MessageTranslator.convertMessage(
Component.translatable("book.invalid.tag", NamedTextColor.DARK_RED),
session.locale()
)
));
tag.put(new ListTag("pages", List.of(invalidTagPage)));
}
}
private boolean isValidWrittenBook(CompoundTag tag) {
if (!(tag.get("title") instanceof StringTag title)) {
return false;
}
if (title.getValue().length() > (MAXIMUM_TITLE_LENGTH * 2)) {
// Java rejects books with titles more than 2x the maximum length allowed in the input box
return false;
}
if (!(tag.get("author") instanceof StringTag)) {
return false;
}
if (!(tag.get("pages") instanceof ListTag pages)) {
return false;
}
for (Tag pageTag : pages) {
if (pageTag instanceof StringTag page) {
if (page.getValue().length() > MAXIMUM_PAGE_LENGTH) {
return false;
}
}
}
return true;
}
}

View File

@ -47,6 +47,7 @@ public final class BlockStateValues {
private static final IntSet ALL_CAULDRONS = new IntOpenHashSet();
private static final Int2IntMap BANNER_COLORS = new FixedInt2IntMap();
private static final Int2ByteMap BED_COLORS = new FixedInt2ByteMap();
private static final Int2IntMap BRUSH_PROGRESS = new Int2IntOpenHashMap();
private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap();
private static final Int2ObjectMap<DoubleChestValue> DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<String> FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>();
@ -98,6 +99,15 @@ public final class BlockStateValues {
return;
}
JsonNode bedrockStates = blockData.get("bedrock_states");
if (bedrockStates != null) {
JsonNode brushedProgress = bedrockStates.get("brushed_progress");
if (brushedProgress != null) {
BRUSH_PROGRESS.put(javaBlockState, brushedProgress.intValue());
return;
}
}
if (javaId.contains("command_block")) {
COMMAND_BLOCK_VALUES.put(javaBlockState, javaId.contains("conditional=true") ? (byte) 1 : (byte) 0);
return;
@ -225,6 +235,17 @@ public final class BlockStateValues {
return BED_COLORS.getOrDefault(state, (byte) -1);
}
/**
* The brush progress of suspicious sand/gravel is not sent by the java server when it updates the block entity.
* Although brush progress is part of the bedrock block state, it must be included in the block entity update.
*
* @param state BlockState of the block
* @return brush progress or 0 if the lookup failed
*/
public static int getBrushProgress(int state) {
return BRUSH_PROGRESS.getOrDefault(state, 0);
}
/**
* Non-water cauldrons (since Bedrock 1.18.30) must have a block entity packet sent on chunk load to fix rendering issues.
*

View File

@ -28,12 +28,8 @@ package org.geysermc.geyser.network;
import com.github.steveice10.mc.protocol.codec.MinecraftCodec;
import com.github.steveice10.mc.protocol.codec.PacketCodec;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.cloudburstmc.protocol.bedrock.codec.v557.Bedrock_v557;
import org.cloudburstmc.protocol.bedrock.codec.v560.Bedrock_v560;
import org.cloudburstmc.protocol.bedrock.codec.v567.Bedrock_v567;
import org.cloudburstmc.protocol.bedrock.codec.v568.Bedrock_v568;
import org.cloudburstmc.protocol.bedrock.codec.v575.Bedrock_v575;
import org.cloudburstmc.protocol.bedrock.codec.v582.Bedrock_v582;
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589;
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
import org.geysermc.geyser.session.GeyserSession;
@ -49,9 +45,8 @@ public final class GameProtocol {
* Default Bedrock codec that should act as a fallback. Should represent the latest available
* release of the game that Geyser supports.
*/
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v582.CODEC.toBuilder()
.minecraftVersion("1.19.81")
.build();
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v589.CODEC;
/**
* A list of all supported Bedrock versions that can join Geyser
*/
@ -64,20 +59,10 @@ public final class GameProtocol {
private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC;
static {
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v557.CODEC.toBuilder()
.minecraftVersion("1.19.40/1.19.41")
.build());
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v560.CODEC.toBuilder()
.minecraftVersion("1.19.50/1.19.51")
.build());
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v567.CODEC);
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v568.CODEC);
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v575.CODEC.toBuilder()
.minecraftVersion("1.19.70/1.19.71/1.19.73")
.build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v582.CODEC.toBuilder()
.minecraftVersion("1.19.80/1.19.81")
.build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
}
/**
@ -96,16 +81,8 @@ public final class GameProtocol {
/* Bedrock convenience methods to gatekeep features and easily remove the check on version removal */
public static boolean supports1_19_50(GeyserSession session) {
return session.getUpstream().getProtocolVersion() >= Bedrock_v560.CODEC.getProtocolVersion();
}
public static boolean supports1_19_60(GeyserSession session) {
return session.getUpstream().getProtocolVersion() >= Bedrock_v567.CODEC.getProtocolVersion();
}
public static boolean supports1_19_80(GeyserSession session) {
return session.getUpstream().getProtocolVersion() >= Bedrock_v582.CODEC.getProtocolVersion();
public static boolean isPre1_20(GeyserSession session) {
return session.getUpstream().getProtocolVersion() < Bedrock_v589.CODEC.getProtocolVersion();
}
/**

View File

@ -47,11 +47,6 @@ public class GeyserServerInitializer extends BedrockServerInitializer {
this.geyser = geyser;
}
@Override
protected void postInitChannel(Channel channel) throws Exception {
super.postInitChannel(channel);
}
@Override
public void initSession(@Nonnull BedrockServerSession bedrockServerSession) {
try {

View File

@ -28,8 +28,6 @@ package org.geysermc.geyser.network;
import io.netty.buffer.Unpooled;
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.cloudburstmc.protocol.bedrock.codec.v567.Bedrock_v567;
import org.cloudburstmc.protocol.bedrock.codec.v568.Bedrock_v568;
import org.cloudburstmc.protocol.bedrock.data.ExperimentData;
import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm;
import org.cloudburstmc.protocol.bedrock.data.ResourcePackType;
@ -51,9 +49,12 @@ import org.cloudburstmc.protocol.common.PacketSignal;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.pack.PackCodec;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.api.pack.ResourcePackManifest;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.pack.ResourcePack;
import org.geysermc.geyser.pack.ResourcePackManifest;
import org.geysermc.geyser.event.type.SessionLoadResourcePacksEventImpl;
import org.geysermc.geyser.pack.GeyserResourcePack;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.GeyserSession;
@ -63,15 +64,20 @@ import org.geysermc.geyser.util.LoginEncryptionUtils;
import org.geysermc.geyser.util.MathUtils;
import org.geysermc.geyser.util.VersionCheckUtils;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.OptionalInt;
public class UpstreamPacketHandler extends LoggingPacketHandler {
private Deque<String> packsToSent = new ArrayDeque<>();
private boolean networkSettingsRequested = false;
private final Deque<String> packsToSent = new ArrayDeque<>();
private SessionLoadResourcePacksEventImpl resourcePackLoadEvent;
public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) {
super(geyser, session);
@ -87,8 +93,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
return translateAndDefault(packet);
}
private boolean newProtocol = false; // TEMPORARY
private boolean setCorrectCodec(int protocolVersion) {
BedrockCodec packetCodec = GameProtocol.getBedrockCodec(protocolVersion);
if (packetCodec == null) {
@ -127,9 +131,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
@Override
public PacketSignal handle(RequestNetworkSettingsPacket packet) {
if (setCorrectCodec(packet.getProtocolVersion())) {
newProtocol = true;
} else {
if (!setCorrectCodec(packet.getProtocolVersion())) {
return PacketSignal.HANDLED;
}
@ -143,6 +145,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
session.getUpstream().getSession().setCompression(algorithm);
session.getUpstream().getSession().setCompressionLevel(this.geyser.getConfig().getBedrock().getCompressionLevel());
networkSettingsRequested = true;
return PacketSignal.HANDLED;
}
@ -154,12 +157,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
return PacketSignal.HANDLED;
}
// session.getUpstream().getSession().getCodec() == null
if (!newProtocol) {
if (!setCorrectCodec(loginPacket.getProtocolVersion())) { // REMOVE WHEN ONLY 1.19.30 IS SUPPORTED OR 1.20
return PacketSignal.HANDLED;
}
if (!networkSettingsRequested) {
session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", GameProtocol.getAllSupportedBedrockVersions()));
return PacketSignal.HANDLED;
}
// Set the block translation based off of version
@ -173,23 +173,22 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
return PacketSignal.HANDLED;
}
// Hack for... whatever this is
if (loginPacket.getProtocolVersion() == Bedrock_v567.CODEC.getProtocolVersion() && !session.getClientData().getGameVersion().equals("1.19.60")) {
session.getUpstream().getSession().setCodec(Bedrock_v568.CODEC);
}
PlayStatusPacket playStatus = new PlayStatusPacket();
playStatus.setStatus(PlayStatusPacket.Status.LOGIN_SUCCESS);
session.sendUpstreamPacket(playStatus);
geyser.getSessionManager().addPendingSession(session);
this.resourcePackLoadEvent = new SessionLoadResourcePacksEventImpl(session, new HashMap<>(Registries.RESOURCE_PACKS.get()));
this.geyser.eventBus().fire(this.resourcePackLoadEvent);
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
for(ResourcePack resourcePack : ResourcePack.PACKS.values()) {
ResourcePackManifest.Header header = resourcePack.getManifest().getHeader();
for (ResourcePack pack : this.resourcePackLoadEvent.resourcePacks()) {
PackCodec codec = pack.codec();
ResourcePackManifest.Header header = pack.manifest().header();
resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(
header.getUuid().toString(), header.getVersionString(), resourcePack.getFile().length(),
resourcePack.getContentKey(), "", header.getUuid().toString(), false, false));
header.uuid().toString(), header.version().toString(), codec.size(), pack.contentKey(),
"", header.uuid().toString(), false, false));
}
resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks());
session.sendUpstreamPacket(resourcePacksInfo);
@ -222,9 +221,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not
stackPacket.setGameVersion(session.getClientData().getGameVersion());
for (ResourcePack pack : ResourcePack.PACKS.values()) {
ResourcePackManifest.Header header = pack.getManifest().getHeader();
stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), header.getVersionString(), ""));
for (ResourcePack pack : this.resourcePackLoadEvent.resourcePacks()) {
ResourcePackManifest.Header header = pack.manifest().header();
stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.uuid().toString(), header.version().toString(), ""));
}
if (GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) {
@ -232,6 +231,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true));
}
if (GameProtocol.isPre1_20(session)) {
stackPacket.getExperiments().add(new ExperimentData("next_major_update", true));
stackPacket.getExperiments().add(new ExperimentData("sniffer", true));
}
session.sendUpstreamPacket(stackPacket);
break;
@ -298,21 +302,22 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
@Override
public PacketSignal handle(ResourcePackChunkRequestPacket packet) {
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
ResourcePack pack = ResourcePack.PACKS.get(packet.getPackId().toString());
ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packet.getPackId().toString());
PackCodec codec = pack.codec();
data.setChunkIndex(packet.getChunkIndex());
data.setProgress(packet.getChunkIndex() * ResourcePack.CHUNK_SIZE);
data.setProgress((long) packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE);
data.setPackVersion(packet.getPackVersion());
data.setPackId(packet.getPackId());
int offset = packet.getChunkIndex() * ResourcePack.CHUNK_SIZE;
long remainingSize = pack.getFile().length() - offset;
byte[] packData = new byte[(int) MathUtils.constrain(remainingSize, 0, ResourcePack.CHUNK_SIZE)];
int offset = packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE;
long remainingSize = codec.size() - offset;
byte[] packData = new byte[(int) MathUtils.constrain(remainingSize, 0, GeyserResourcePack.CHUNK_SIZE)];
try (InputStream inputStream = new FileInputStream(pack.getFile())) {
inputStream.skip(offset);
inputStream.read(packData, 0, packData.length);
} catch (Exception e) {
try (SeekableByteChannel channel = codec.serialize(pack)) {
channel.position(offset);
channel.read(ByteBuffer.wrap(packData, 0, packData.length));
} catch (IOException e) {
e.printStackTrace();
}
@ -321,7 +326,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
session.sendUpstreamPacket(data);
// Check if it is the last chunk and send next pack in queue when available.
if (remainingSize <= ResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) {
if (remainingSize <= GeyserResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) {
sendPackDataInfo(packsToSent.pop());
}
@ -331,15 +336,16 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
private void sendPackDataInfo(String id) {
ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket();
String[] packID = id.split("_");
ResourcePack pack = ResourcePack.PACKS.get(packID[0]);
ResourcePackManifest.Header header = pack.getManifest().getHeader();
ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packID[0]);
PackCodec codec = pack.codec();
ResourcePackManifest.Header header = pack.manifest().header();
data.setPackId(header.getUuid());
int chunkCount = (int) Math.ceil((int) pack.getFile().length() / (double) ResourcePack.CHUNK_SIZE);
data.setPackId(header.uuid());
int chunkCount = (int) Math.ceil(codec.size() / (double) GeyserResourcePack.CHUNK_SIZE);
data.setChunkCount(chunkCount);
data.setCompressedPackSize(pack.getFile().length());
data.setMaxChunkSize(ResourcePack.CHUNK_SIZE);
data.setHash(pack.getSha256());
data.setCompressedPackSize(codec.size());
data.setMaxChunkSize(GeyserResourcePack.CHUNK_SIZE);
data.setHash(codec.sha256());
data.setPackVersion(packID[1]);
data.setPremium(false);
data.setType(ResourcePackType.RESOURCES);

View File

@ -187,7 +187,16 @@ public final class GeyserServer {
public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) {
String ip = geyser.getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : "<IP address withheld>";
String ip;
if (geyser.getConfig().isLogPlayerIpAddresses()) {
if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString();
} else {
ip = inetSocketAddress.toString();
}
} else {
ip = "<IP address withheld>";
}
geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", ip));
}

View File

@ -73,10 +73,10 @@ public class ProxyServerHandler extends SimpleChannelInboundHandler<DatagramPack
presentAddress = new InetSocketAddress(decoded.sourceAddress(), decoded.sourcePort());
log.debug("Got PROXY header: (from {}) {}", packet.sender(), presentAddress);
GeyserImpl.getInstance().getGeyserServer().getProxiedAddresses().put(packet.sender(), presentAddress);
return;
} else {
log.trace("Reusing PROXY header: (from {}) {}", packet.sender(), presentAddress);
}
log.trace("Reusing PROXY header: (from {}) {}", packet.sender(), presentAddress);
ctx.fireChannelRead(packet.retain());
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.pack;
import org.geysermc.geyser.api.pack.PackCodec;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.api.pack.ResourcePackManifest;
public record GeyserResourcePack(PackCodec codec, ResourcePackManifest manifest, String contentKey) implements ResourcePack {
/**
* The size of each chunk to use when sending the resource packs to clients in bytes
*/
public static final int CHUNK_SIZE = 102400;
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.pack;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.geysermc.geyser.api.pack.ResourcePackManifest;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Collection;
import java.util.UUID;
public record GeyserResourcePackManifest(@JsonProperty("format_version") int formatVersion, Header header, Collection<Module> modules, Collection<Dependency> dependencies) implements ResourcePackManifest {
public record Header(UUID uuid, Version version, String name, String description, @JsonProperty("min_engine_version") Version minimumSupportedMinecraftVersion) implements ResourcePackManifest.Header { }
public record Module(UUID uuid, Version version, String type, String description) implements ResourcePackManifest.Module { }
public record Dependency(UUID uuid, Version version) implements ResourcePackManifest.Dependency { }
@JsonDeserialize(using = Version.VersionDeserializer.class)
public record Version(int major, int minor, int patch) implements ResourcePackManifest.Version {
@Override
public @NotNull String toString() {
return major + "." + minor + "." + patch;
}
public static class VersionDeserializer extends JsonDeserializer<Version> {
@Override
public Version deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
int[] version = ctxt.readValue(p, int[].class);
return new Version(version[0], version[1], version[2]);
}
}
}
}

View File

@ -1,160 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.pack;
import lombok.Getter;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* This represents a resource pack and all the data relevant to it
*/
public class ResourcePack {
/**
* The list of loaded resource packs
*/
public static final Map<String, ResourcePack> PACKS = new HashMap<>();
/**
* The size of each chunk to use when sending the resource packs to clients in bytes
*/
public static final int CHUNK_SIZE = 102400;
private byte[] sha256;
private File file;
private ResourcePackManifest manifest;
private ResourcePackManifest.Version version;
@Getter
private String contentKey;
/**
* Loop through the packs directory and locate valid resource pack files
*/
public static void loadPacks() {
Path directory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("packs");
if (!Files.exists(directory)) {
try {
Files.createDirectory(directory);
} catch (IOException e) {
GeyserImpl.getInstance().getLogger().error("Could not create packs directory", e);
}
// As we just created the directory it will be empty
return;
}
List<Path> resourcePacks;
try {
resourcePacks = Files.walk(directory).collect(Collectors.toList());
} catch (IOException e) {
GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e);
return;
}
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
GeyserImpl.getInstance().eventBus().fire(event);
for (Path path : event.resourcePacks()) {
File file = path.toFile();
if (file.getName().endsWith(".zip") || file.getName().endsWith(".mcpack")) {
ResourcePack pack = new ResourcePack();
pack.sha256 = FileUtils.calculateSHA256(file);
try (ZipFile zip = new ZipFile(file);
Stream<? extends ZipEntry> stream = zip.stream()) {
stream.forEach((x) -> {
String name = x.getName();
if (name.length() >= 80) {
GeyserImpl.getInstance().getLogger().warning("The resource pack " + file.getName()
+ " has a file in it that meets or exceeds 80 characters in its path (" + name
+ ", " + name.length() + " characters long). This will cause problems on some Bedrock platforms." +
" Please rename it to be shorter, or reduce the amount of folders needed to get to the file.");
}
if (name.contains("manifest.json")) {
try {
ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
// Sometimes a pack_manifest file is present and not in a valid format,
// but a manifest file is, so we null check through that one
if (manifest.getHeader().getUuid() != null) {
pack.file = file;
pack.manifest = manifest;
pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion());
PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
// Check if a file exists with the same name as the resource pack suffixed by .key,
// and set this as content key. (e.g. test.zip, key file would be test.zip.key)
File keyFile = new File(file.getParentFile(), file.getName() + ".key");
pack.contentKey = keyFile.exists() ? Files.readString(keyFile.toPath(), StandardCharsets.UTF_8) : "";
} catch (Exception e) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", file.getName()));
e.printStackTrace();
}
}
}
}
public byte[] getSha256() {
return sha256;
}
public File getFile() {
return file;
}
public ResourcePackManifest getManifest() {
return manifest;
}
public ResourcePackManifest.Version getVersion() {
return version;
}
}

View File

@ -1,117 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.pack;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.Value;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
/**
* author: NukkitX
* Nukkit Project
*/
@Getter
@EqualsAndHashCode
public class ResourcePackManifest {
@JsonProperty("format_version")
private Integer formatVersion;
private Header header;
private Collection<Module> modules;
protected Collection<Dependency> dependencies;
public Collection<Module> getModules() {
return Collections.unmodifiableCollection(modules);
}
@Getter
@ToString
public static class Header {
private String description;
private String name;
private UUID uuid;
private int[] version;
@JsonProperty("min_engine_version")
private int[] minimumSupportedMinecraftVersion;
public String getVersionString() {
return version[0] + "." + version[1] + "." + version[2];
}
}
@Getter
@ToString
public static class Module {
private String description;
private String name;
private UUID uuid;
private int[] version;
}
@Getter
@ToString
public static class Dependency {
private UUID uuid;
private int[] version;
}
@Value
public static class Version {
private final int major;
private final int minor;
private final int patch;
public static Version fromString(String ver) {
String[] split = ver.replace(']', ' ')
.replace('[', ' ')
.replaceAll(" ", "").split(",");
return new Version(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2]));
}
public static Version fromArray(int[] ver) {
return new Version(ver[0], ver[1], ver[2]);
}
private Version(int major, int minor, int patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
}
@Override
public String toString() {
return major + "." + minor + "." + patch;
}
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.pack.path;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.pack.PathPackCodec;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
import org.geysermc.geyser.util.FileUtils;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
@RequiredArgsConstructor
public class GeyserPathPackCodec extends PathPackCodec {
private final Path path;
private FileTime lastModified;
private byte[] sha256;
private long size = -1;
@Override
public @NonNull Path path() {
this.checkLastModified();
return this.path;
}
@Override
public byte @NonNull [] sha256() {
this.checkLastModified();
if (this.sha256 != null) {
return this.sha256;
}
return this.sha256 = FileUtils.calculateSHA256(this.path);
}
@Override
public long size() {
this.checkLastModified();
if (this.size != -1) {
return this.size;
}
try {
return this.size = Files.size(this.path);
} catch (IOException e) {
throw new RuntimeException("Could not get file size of path " + this.path, e);
}
}
@Override
public @NonNull SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException {
return FileChannel.open(this.path);
}
@Override
protected @NonNull ResourcePack create() {
return ResourcePackLoader.readPack(this.path);
}
private void checkLastModified() {
try {
FileTime lastModified = Files.getLastModifiedTime(this.path);
if (this.lastModified == null) {
this.lastModified = lastModified;
return;
}
if (lastModified.toInstant().isAfter(this.lastModified.toInstant())) {
GeyserImpl.getInstance().getLogger().warning("Detected a change in the resource pack " + path + ". This is likely to cause undefined behavior for new clients joining. It is suggested you restart Geyser.");
this.lastModified = lastModified;
this.sha256 = null;
this.size = -1;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,184 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.registry;
import org.geysermc.geyser.registry.loader.RegistryLoader;
import org.geysermc.geyser.registry.loader.RegistryLoaders;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* A deferred registry is a registry that is not loaded until it is needed.
* This is useful for registries that are not needed until after other parts
* of the lifecycle have been completed.
* <p>
* This class is slightly different from other registries in that it acts as
* a wrapper around another registry. This is to allow for any kind of registry
* type to be deferred.
*
* @param <M> the value being held by the registry
*/
public final class DeferredRegistry<M> implements IRegistry<M> {
private final Registry<M> backingRegistry;
private final Supplier<M> loader;
private boolean loaded;
private <I> DeferredRegistry(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, RegistryLoader<I, M> deferredLoader) {
this.backingRegistry = registryLoader.apply(RegistryLoaders.uninitialized());
this.loader = () -> deferredLoader.load(null);
}
private <I> DeferredRegistry(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, Supplier<RegistryLoader<I, M>> deferredLoader) {
this.backingRegistry = registryLoader.apply(RegistryLoaders.uninitialized());
this.loader = () -> deferredLoader.get().load(null);
}
private <I> DeferredRegistry(I input, RegistryInitializer<M> registryInitializer, RegistryLoader<I, M> deferredLoader) {
this.backingRegistry = registryInitializer.initialize(input, RegistryLoaders.uninitialized());
this.loader = () -> deferredLoader.load(input);
}
private <I> DeferredRegistry(I input, RegistryInitializer<M> registryInitializer, Supplier<RegistryLoader<I, M>> deferredLoader) {
this.backingRegistry = registryInitializer.initialize(input, RegistryLoaders.uninitialized());
this.loader = () -> deferredLoader.get().load(input);
}
/**
* Gets the underlying value held by this registry.
*
* @return the underlying value held by this registry
* @throws IllegalStateException if this deferred registry has not been loaded yet
*/
@Override
public M get() {
if (!this.loaded) {
throw new IllegalStateException("Registry has not been loaded yet!");
}
return this.backingRegistry.get();
}
@Override
public void set(M mappings) {
this.backingRegistry.set(mappings);
}
/**
* Registers what is specified in the given {@link Consumer} into the underlying value.
*
* @param consumer the consumer
* @throws IllegalStateException if this deferred registry has not been loaded yet
*/
@Override
public void register(Consumer<M> consumer) {
if (!this.loaded) {
throw new IllegalStateException("Registry has not been loaded yet!");
}
this.backingRegistry.register(consumer);
}
/**
* Loads the registry.
*/
public void load() {
this.backingRegistry.set(this.loader.get());
this.loaded = true;
}
/**
* Creates a new deferred registry.
*
* @param registryLoader the registry loader
* @param deferredLoader the deferred loader
* @param <I> the input type
* @param <M> the registry type
* @return the new deferred registry
*/
public static <I, M> DeferredRegistry<M> create(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, RegistryLoader<I, M> deferredLoader) {
return new DeferredRegistry<>(registryLoader, deferredLoader);
}
/**
* Creates a new deferred registry.
*
* @param registryLoader the registry loader
* @param deferredLoader the deferred loader
* @param <I> the input type
* @param <M> the registry type
* @return the new deferred registry
*/
public static <I, M> DeferredRegistry<M> create(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, Supplier<RegistryLoader<I, M>> deferredLoader) {
return new DeferredRegistry<>(registryLoader, deferredLoader);
}
/**
* Creates a new deferred registry.
*
* @param registryInitializer the registry initializer
* @param deferredLoader the deferred loader
* @param <I> the input type
* @param <M> the registry type
* @return the new deferred registry
*/
public static <I, M> DeferredRegistry<M> create(I input, RegistryInitializer<M> registryInitializer, RegistryLoader<I, M> deferredLoader) {
return new DeferredRegistry<>(input, registryInitializer, deferredLoader);
}
/**
* Creates a new deferred registry.
*
* @param registryInitializer the registry initializer
* @param deferredLoader the deferred loader
* @param <I> the input type
* @param <M> the registry type
* @return the new deferred registry
*/
public static <I, M> DeferredRegistry<M> create(I input, RegistryInitializer<M> registryInitializer, Supplier<RegistryLoader<I, M>> deferredLoader) {
return new DeferredRegistry<>(input, registryInitializer, deferredLoader);
}
/**
* A registry initializer.
*
* @param <M> the registry type
*/
interface RegistryInitializer<M> {
/**
* Initializes the registry.
*
* @param input the input
* @param registryLoader the registry loader
* @param <I> the input type
* @return the initialized registry
*/
<I> Registry<M> initialize(I input, RegistryLoader<I, M> registryLoader);
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.registry;
import java.util.function.Consumer;
/**
* Represents a registry.
*
* @param <M> the value being held by the registry
*/
interface IRegistry<M> {
/**
* Gets the underlying value held by this registry.
*
* @return the underlying value held by this registry.
*/
M get();
/**
* Sets the underlying value held by this registry.
* Clears any existing data associated with the previous
* value.
*
* @param mappings the underlying value held by this registry
*/
void set(M mappings);
/**
* Registers what is specified in the given
* {@link Consumer} into the underlying value.
*
* @param consumer the consumer
*/
void register(Consumer<M> consumer);
}

View File

@ -25,6 +25,7 @@
package org.geysermc.geyser.registry;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundDelimiterPacket;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundTabListPacket;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLightUpdatePacket;
import io.netty.channel.EventLoop;
@ -44,6 +45,7 @@ public class PacketTranslatorRegistry<T> extends AbstractMappedRegistry<Class<?
static {
IGNORED_PACKETS.add(ClientboundLightUpdatePacket.class); // Light is handled on Bedrock for us
IGNORED_PACKETS.add(ClientboundTabListPacket.class); // Cant be implemented in Bedrock
IGNORED_PACKETS.add(ClientboundDelimiterPacket.class); // Not implemented, spams logs
}
protected PacketTranslatorRegistry() {

View File

@ -41,6 +41,8 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.PotionMixData;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeData;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
@ -158,6 +160,11 @@ public final class Registries {
*/
public static final IntMappedRegistry<org.cloudburstmc.protocol.bedrock.data.SoundEvent> RECORDS = IntMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
/**
* A mapped registry holding {@link ResourcePack}'s with the pack uuid as keys.
*/
public static final DeferredRegistry<Map<String, ResourcePack>> RESOURCE_PACKS = DeferredRegistry.create(GeyserImpl.getInstance().packDirectory(), SimpleMappedRegistry::create, RegistryLoaders.RESOURCE_PACKS);
/**
* A mapped registry holding sound identifiers to their corresponding {@link SoundMapping}.
*/

View File

@ -64,7 +64,7 @@ import java.util.function.Consumer;
*
* @param <M> the value being held by the registry
*/
public abstract class Registry<M> {
public abstract class Registry<M> implements IRegistry<M> {
protected M mappings;
/**
@ -85,6 +85,7 @@ public abstract class Registry<M> {
*
* @return the underlying value held by this registry.
*/
@Override
public M get() {
return this.mappings;
}
@ -96,6 +97,7 @@ public abstract class Registry<M> {
*
* @param mappings the underlying value held by this registry
*/
@Override
public void set(M mappings) {
this.mappings = mappings;
}
@ -106,6 +108,7 @@ public abstract class Registry<M> {
*
* @param consumer the consumer
*/
@Override
public void register(Consumer<M> consumer) {
consumer.accept(this.mappings);
}

View File

@ -31,13 +31,16 @@ import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.item.custom.CustomItemData;
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
import org.geysermc.geyser.api.pack.PathPackCodec;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.event.GeyserEventRegistrar;
import org.geysermc.geyser.item.GeyserCustomItemData;
import org.geysermc.geyser.item.GeyserCustomItemOptions;
import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData;
import org.geysermc.geyser.pack.path.GeyserPathPackCodec;
import org.geysermc.geyser.registry.provider.ProviderSupplier;
import java.nio.file.Path;
import java.util.Map;
/**
@ -47,11 +50,15 @@ public class ProviderRegistryLoader implements RegistryLoader<Map<Class<?>, Prov
@Override
public Map<Class<?>, ProviderSupplier> load(Map<Class<?>, ProviderSupplier> providers) {
// misc
providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0]));
providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0]));
providers.put(PathPackCodec.class, args -> new GeyserPathPackCodec((Path) args[0]));
// items
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder());
providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder());
providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder());
providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0]));
return providers;
}

View File

@ -36,7 +36,12 @@ public final class RegistryLoaders {
/**
* The {@link RegistryLoader} responsible for loading NBT.
*/
public static NbtRegistryLoader NBT = new NbtRegistryLoader();
public static final NbtRegistryLoader NBT = new NbtRegistryLoader();
/**
* The {@link RegistryLoader} responsible for loading resource packs.
*/
public static final ResourcePackLoader RESOURCE_PACKS = new ResourcePackLoader();
/**
* Wraps the surrounding {@link Supplier} in a {@link RegistryLoader} which does
@ -51,10 +56,14 @@ public final class RegistryLoaders {
}
/**
* Returns a {@link RegistryLoader} which has not taken
* in any input value.
*
* @param <I> the input
* @param <V> the value
* @return a RegistryLoader that is yet to contain a value.
*/
public static <V> RegistryLoader<Object, V> uninitialized() {
public static <I, V> RegistryLoader<I, V> uninitialized() {
return input -> null;
}

View File

@ -0,0 +1,156 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.registry.loader;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.pack.GeyserResourcePack;
import org.geysermc.geyser.pack.GeyserResourcePackManifest;
import org.geysermc.geyser.pack.path.GeyserPathPackCodec;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Loads {@link ResourcePack}s within a {@link Path} directory, firing the {@link GeyserLoadResourcePacksEvent}.
*/
public class ResourcePackLoader implements RegistryLoader<Path, Map<String, ResourcePack>> {
static final PathMatcher PACK_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**.{zip,mcpack}");
private static final boolean SHOW_RESOURCE_PACK_LENGTH_WARNING = Boolean.parseBoolean(System.getProperty("Geyser.ShowResourcePackLengthWarning", "true"));
/**
* Loop through the packs directory and locate valid resource pack files
*/
@Override
public Map<String, ResourcePack> load(Path directory) {
Map<String, ResourcePack> packMap = new HashMap<>();
if (!Files.exists(directory)) {
try {
Files.createDirectory(directory);
} catch (IOException e) {
GeyserImpl.getInstance().getLogger().error("Could not create packs directory", e);
}
}
List<Path> resourcePacks;
try (Stream<Path> stream = Files.walk(directory)) {
resourcePacks = stream.filter(PACK_MATCHER::matches)
.collect(Collectors.toCollection(ArrayList::new)); // toList() does not guarantee mutability
} catch (Exception e) {
GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e);
// Ensure the event is fired even if there was an issue reading
// from our own resource pack directory. External projects may have
// resource packs located at different locations.
resourcePacks = new ArrayList<>();
}
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
GeyserImpl.getInstance().eventBus().fire(event);
for (Path path : event.resourcePacks()) {
try {
GeyserResourcePack pack = readPack(path);
packMap.put(pack.manifest().header().uuid().toString(), pack);
} catch (Exception e) {
e.printStackTrace();
}
}
return packMap;
}
/**
* Reads a resource pack at the given file. Also searches for a file in the same directory, with the same name
* but suffixed by ".key", containing the content key. If such file does not exist, no content key is stored.
*
* @param path the file to read from, in ZIP format
* @return a {@link ResourcePack} representation
* @throws IllegalArgumentException if the pack manifest was invalid or there was any processing exception
*/
public static GeyserResourcePack readPack(Path path) throws IllegalArgumentException {
if (!path.getFileName().toString().endsWith(".mcpack") && !path.getFileName().toString().endsWith(".zip")) {
throw new IllegalArgumentException("Resource pack " + path.getFileName() + " must be a .zip or .mcpack file!");
}
AtomicReference<GeyserResourcePackManifest> manifestReference = new AtomicReference<>();
try (ZipFile zip = new ZipFile(path.toFile());
Stream<? extends ZipEntry> stream = zip.stream()) {
stream.forEach(x -> {
String name = x.getName();
if (SHOW_RESOURCE_PACK_LENGTH_WARNING && name.length() >= 80) {
GeyserImpl.getInstance().getLogger().warning("The resource pack " + path.getFileName()
+ " has a file in it that meets or exceeds 80 characters in its path (" + name
+ ", " + name.length() + " characters long). This will cause problems on some Bedrock platforms." +
" Please rename it to be shorter, or reduce the amount of folders needed to get to the file.");
}
if (name.contains("manifest.json")) {
try {
GeyserResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), GeyserResourcePackManifest.class);
if (manifest.header().uuid() != null) {
manifestReference.set(manifest);
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
GeyserResourcePackManifest manifest = manifestReference.get();
if (manifest == null) {
throw new IllegalArgumentException(path.getFileName() + " does not contain a valid pack_manifest.json or manifest.json");
}
// Check if a file exists with the same name as the resource pack suffixed by .key,
// and set this as content key. (e.g. test.zip, key file would be test.zip.key)
Path keyFile = path.resolveSibling(path.getFileName().toString() + ".key");
String contentKey = Files.exists(keyFile) ? Files.readString(path, StandardCharsets.UTF_8) : "";
return new GeyserResourcePack(new GeyserPathPackCodec(path), manifest, contentKey);
} catch (Exception e) {
throw new IllegalArgumentException(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", path.getFileName()), e);
}
}
}

View File

@ -32,11 +32,8 @@ import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import it.unimi.dsi.fastutil.objects.*;
import org.cloudburstmc.nbt.*;
import org.cloudburstmc.protocol.bedrock.codec.v544.Bedrock_v544;
import org.cloudburstmc.protocol.bedrock.codec.v560.Bedrock_v560;
import org.cloudburstmc.protocol.bedrock.codec.v567.Bedrock_v567;
import org.cloudburstmc.protocol.bedrock.codec.v575.Bedrock_v575;
import org.cloudburstmc.protocol.bedrock.codec.v582.Bedrock_v582;
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.level.block.BlockStateValues;
@ -70,41 +67,54 @@ public final class BlockRegistryPopulator {
}
private static void registerBedrockBlocks() {
BiFunction<String, NbtMapBuilder, String> woolMapper = (bedrockIdentifier, statesBuilder) -> {
if (bedrockIdentifier.equals("minecraft:wool")) {
String color = (String) statesBuilder.remove("color");
if ("silver".equals(color)) {
color = "light_gray";
BiFunction<String, NbtMapBuilder, String> emptyMapper = (bedrockIdentifier, statesBuilder) -> null;
// We are using mappings that directly support 1.20, so this maps it back to 1.19.80
BiFunction<String, NbtMapBuilder, String> legacyMapper = (bedrockIdentifier, statesBuilder) -> {
if (bedrockIdentifier.endsWith("pumpkin")) {
String direction = statesBuilder.remove("minecraft:cardinal_direction").toString();
statesBuilder.putInt("direction", switch (direction) {
case "north" -> 2;
case "east" -> 3;
case "west" -> 1;
default -> 0; // south
});
} else if (bedrockIdentifier.endsWith("carpet") && !bedrockIdentifier.startsWith("minecraft:moss")) {
String color = bedrockIdentifier.replace("minecraft:", "").replace("_carpet", "");
if (color.equals("light_gray")) {
color = "silver";
}
return "minecraft:" + color + "_wool";
statesBuilder.putString("color", color);
return "minecraft:carpet";
} else if (bedrockIdentifier.equals("minecraft:sniffer_egg")) {
statesBuilder.remove("cracked_state");
return "minecraft:dragon_egg";
} else if (bedrockIdentifier.endsWith("coral")) {
statesBuilder.putString("coral_color", "blue"); // all blue
statesBuilder.putBoolean("dead_bit", bedrockIdentifier.startsWith("minecraft:dead"));
return "minecraft:coral";
} else if (bedrockIdentifier.endsWith("sculk_sensor")) {
int phase = (int) statesBuilder.remove("sculk_sensor_phase");
statesBuilder.putBoolean("powered_bit", phase != 0);
} else if (bedrockIdentifier.endsWith("pitcher_plant")) {
statesBuilder.putString("double_plant_type", "sunflower");
return "minecraft:double_plant";
} else if (bedrockIdentifier.endsWith("pitcher_crop")) {
statesBuilder.remove("growth");
if (((byte) statesBuilder.remove("upper_block_bit")) == 1){
statesBuilder.putString("flower_type", "orchid");
return "minecraft:red_flower"; // top
}
statesBuilder.putBoolean("update_bit", false);
return "minecraft:flower_pot"; // bottom
}
return null;
};
BiFunction<String, NbtMapBuilder, String> emptyMapper = (bedrockIdentifier, statesBuilder) -> null;
ImmutableMap<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> blockMappers = ImmutableMap.<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>>builder()
.put(ObjectIntPair.of("1_19_20", Bedrock_v544.CODEC.getProtocolVersion()), emptyMapper)
.put(ObjectIntPair.of("1_19_50", Bedrock_v560.CODEC.getProtocolVersion()), emptyMapper)
.put(ObjectIntPair.of("1_19_60", Bedrock_v567.CODEC.getProtocolVersion()), emptyMapper)
.put(ObjectIntPair.of("1_19_70", Bedrock_v575.CODEC.getProtocolVersion()), woolMapper)
.put(ObjectIntPair.of("1_19_80", Bedrock_v582.CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> {
String identifier = woolMapper.apply(bedrockIdentifier, statesBuilder);
if (identifier != null) {
return identifier;
}
switch (bedrockIdentifier) {
case "minecraft:log", "minecraft:log2" -> {
String woodType = (String) statesBuilder.remove(bedrockIdentifier.equals("minecraft:log") ? "old_log_type" : "new_log_type");
return "minecraft:" + woodType + "_log";
}
case "minecraft:fence" -> {
String woodType = (String) statesBuilder.remove("wood_type");
return "minecraft:" + woodType + "_fence";
}
default -> {
return null;
}
}
})
.put(ObjectIntPair.of("1_19_80", Bedrock_v582.CODEC.getProtocolVersion()), legacyMapper)
.put(ObjectIntPair.of("1_20_0", Bedrock_v589.CODEC.getProtocolVersion()), emptyMapper)
.build();
// We can keep this strong as nothing should be garbage collected
@ -167,8 +177,8 @@ public final class BlockRegistryPopulator {
GeyserBedrockBlock bedrockDefinition = blockStateOrderedMap.get(buildBedrockState(entry.getValue(), stateVersion, stateMapper));
if (bedrockDefinition == null) {
throw new RuntimeException("Unable to find " + javaId + " Bedrock BlockDefinition! Built NBT tag: \n" +
buildBedrockState(entry.getValue(), stateVersion, stateMapper));
throw new RuntimeException("Unable to find " + javaId + " Bedrock BlockDefinition on version "
+ palette.getKey().key() + "! Built NBT tag: \n" + buildBedrockState(entry.getValue(), stateVersion, stateMapper));
}
switch (javaId) {

View File

@ -54,18 +54,18 @@ public class CreativeItemRegistryPopulator {
(identifier, data) -> identifier.equals("minecraft:bordure_indented_banner_pattern") || identifier.equals("minecraft:field_masoned_banner_pattern")
);
static void populate(Map.Entry<String, ItemRegistryPopulator.PaletteVersion> version, Map<String, ItemDefinition> definitions, Consumer<ItemData.Builder> itemConsumer) {
static void populate(ItemRegistryPopulator.PaletteVersion palette, Map<String, ItemDefinition> definitions, Consumer<ItemData.Builder> itemConsumer) {
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
// Load creative items
JsonNode creativeItemEntries;
try (InputStream stream = bootstrap.getResource(String.format("bedrock/creative_items.%s.json", version.getKey()))) {
try (InputStream stream = bootstrap.getResource(String.format("bedrock/creative_items.%s.json", palette.version()))) {
creativeItemEntries = GeyserImpl.JSON_MAPPER.readTree(stream).get("items");
} catch (Exception e) {
throw new AssertionError("Unable to load creative items", e);
}
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(version.getValue().protocolVersion());
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.protocolVersion());
for (JsonNode itemNode : creativeItemEntries) {
ItemData.Builder itemBuilder = createItemData(itemNode, blockMappings, definitions);
if (itemBuilder == null) {

View File

@ -34,14 +34,12 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.*;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType;
import org.cloudburstmc.protocol.bedrock.codec.v544.Bedrock_v544;
import org.cloudburstmc.protocol.bedrock.codec.v560.Bedrock_v560;
import org.cloudburstmc.protocol.bedrock.codec.v567.Bedrock_v567;
import org.cloudburstmc.protocol.bedrock.codec.v575.Bedrock_v575;
import org.cloudburstmc.protocol.bedrock.codec.v582.Bedrock_v582;
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition;
@ -70,22 +68,40 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
public class ItemRegistryPopulator {
record PaletteVersion(int protocolVersion, Map<Item, String> additionalTranslatedItems) {
record PaletteVersion(String version, int protocolVersion, Map<Item, String> javaOnlyItems, Remapper remapper) {
public PaletteVersion(String version, int protocolVersion) {
this(version, protocolVersion, Collections.emptyMap(), (item, mapping) -> mapping);
}
}
@FunctionalInterface
interface Remapper {
@NonNull
GeyserMappingItem remap(Item item, GeyserMappingItem mapping);
}
public static void populate() {
Map<Item, String> manualFallback = new HashMap<>();
manualFallback.put(Items.ENDER_DRAGON_SPAWN_EGG, "minecraft:enderman_spawn_egg");
manualFallback.put(Items.WITHER_SPAWN_EGG, "minecraft:wither_skeleton_spawn_egg");
manualFallback.put(Items.SNOW_GOLEM_SPAWN_EGG, "minecraft:polar_bear_spawn_egg");
manualFallback.put(Items.IRON_GOLEM_SPAWN_EGG, "minecraft:villager_spawn_egg");
Map<Item, String> legacyJavaOnly = new HashMap<>();
legacyJavaOnly.put(Items.MUSIC_DISC_RELIC, "minecraft:music_disc_wait");
legacyJavaOnly.put(Items.PITCHER_PLANT, "minecraft:chorus_flower");
legacyJavaOnly.put(Items.PITCHER_POD, "minecraft:beetroot");
legacyJavaOnly.put(Items.SNIFFER_EGG, "minecraft:sniffer_spawn_egg"); // the BlockItem of the sniffer egg block
Map<String, PaletteVersion> paletteVersions = new Object2ObjectOpenHashMap<>();
paletteVersions.put("1_19_20", new PaletteVersion(Bedrock_v544.CODEC.getProtocolVersion(), manualFallback));
paletteVersions.put("1_19_50", new PaletteVersion(Bedrock_v560.CODEC.getProtocolVersion(), manualFallback));
paletteVersions.put("1_19_60", new PaletteVersion(Bedrock_v567.CODEC.getProtocolVersion(), Collections.emptyMap()));
paletteVersions.put("1_19_70", new PaletteVersion(Bedrock_v575.CODEC.getProtocolVersion(), Collections.emptyMap()));
paletteVersions.put("1_19_80", new PaletteVersion(Bedrock_v582.CODEC.getProtocolVersion(), Collections.emptyMap()));
List<PaletteVersion> paletteVersions = new ArrayList<>(2);
paletteVersions.add(new PaletteVersion("1_19_80", Bedrock_v582.CODEC.getProtocolVersion(), legacyJavaOnly, (item, mapping) -> {
String id = item.javaIdentifier();
if (id.endsWith("pottery_sherd")) {
return mapping.withBedrockIdentifier(id.replace("sherd", "shard"));
} else if (id.endsWith("carpet") && !id.startsWith("minecraft:moss")) {
return mapping.withBedrockIdentifier("minecraft:carpet");
} else if (id.endsWith("coral")) {
return mapping.withBedrockIdentifier("minecraft:coral");
}
return mapping;
}));
paletteVersions.add(new PaletteVersion("1_20_0", Bedrock_v589.CODEC.getProtocolVersion()));
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
@ -115,11 +131,11 @@ public class ItemRegistryPopulator {
boolean firstMappingsPass = true;
/* Load item palette */
for (Map.Entry<String, PaletteVersion> palette : paletteVersions.entrySet()) {
for (PaletteVersion palette : paletteVersions) {
TypeReference<List<PaletteItem>> paletteEntriesType = new TypeReference<>() {};
List<PaletteItem> itemEntries;
try (InputStream stream = bootstrap.getResource(String.format("bedrock/runtime_item_states.%s.json", palette.getKey()))) {
try (InputStream stream = bootstrap.getResource(String.format("bedrock/runtime_item_states.%s.json", palette.version()))) {
itemEntries = GeyserImpl.JSON_MAPPER.readValue(stream, paletteEntriesType);
} catch (Exception e) {
throw new AssertionError("Unable to load Bedrock runtime item IDs", e);
@ -177,22 +193,16 @@ public class ItemRegistryPopulator {
}
});
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.getValue().protocolVersion());
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.protocolVersion());
Set<Item> javaOnlyItems = new ObjectOpenHashSet<>();
Collections.addAll(javaOnlyItems, Items.SPECTRAL_ARROW, Items.DEBUG_STICK,
Items.KNOWLEDGE_BOOK, Items.TIPPED_ARROW, Items.BUNDLE);
// these spawn eggs exist in 1.19.60+;
if (palette.getValue().protocolVersion() < Bedrock_v567.CODEC.getProtocolVersion()) {
Collections.addAll(javaOnlyItems, Items.IRON_GOLEM_SPAWN_EGG, Items.SNOW_GOLEM_SPAWN_EGG,
Items.WITHER_SPAWN_EGG, Items.ENDER_DRAGON_SPAWN_EGG);
}
javaOnlyItems.add(Items.DECORATED_POT);
if (!customItemsAllowed) {
javaOnlyItems.add(Items.FURNACE_MINECART);
}
// Java-only items for this version
javaOnlyItems.addAll(palette.getValue().additionalTranslatedItems().keySet());
javaOnlyItems.addAll(palette.javaOnlyItems().keySet());
Int2ObjectMap<String> customIdMappings = new Int2ObjectOpenHashMap<>();
Set<String> registeredItemNames = new ObjectOpenHashSet<>(); // This is used to check for duplicate item names
@ -203,12 +213,12 @@ public class ItemRegistryPopulator {
throw new RuntimeException("Extra item in mappings? " + entry.getKey());
}
GeyserMappingItem mappingItem;
String replacementItem = palette.getValue().additionalTranslatedItems().get(javaItem);
String replacementItem = palette.javaOnlyItems().get(javaItem);
if (replacementItem != null) {
mappingItem = items.get(replacementItem);
mappingItem = items.get(replacementItem); // java only item, a java id fallback has been provided
} else {
// This items has a mapping specifically for this version of the game
mappingItem = entry.getValue();
// check if any mapping changes need to be made on this version
mappingItem = palette.remapper().remap(javaItem, entry.getValue());
}
if (customItemsAllowed && javaItem == Items.FURNACE_MINECART) {
@ -217,26 +227,10 @@ public class ItemRegistryPopulator {
continue;
}
String bedrockIdentifier;
// 1.19.70+
if (palette.getValue().protocolVersion() >= Bedrock_v575.CODEC.getProtocolVersion() && mappingItem.getBedrockIdentifier().equals("minecraft:wool")) {
bedrockIdentifier = javaItem.javaIdentifier();
} else {
bedrockIdentifier = mappingItem.getBedrockIdentifier();
}
//1.19.80+
if (palette.getValue().protocolVersion >= Bedrock_v582.CODEC.getProtocolVersion()) {
if (mappingItem.getBedrockIdentifier().equals("minecraft:log") ||
mappingItem.getBedrockIdentifier().equals("minecraft:log2") ||
mappingItem.getBedrockIdentifier().equals("minecraft:fence")) {
bedrockIdentifier = javaItem.javaIdentifier();
}
}
String bedrockIdentifier = mappingItem.getBedrockIdentifier();
ItemDefinition definition = definitions.get(bedrockIdentifier);
if (definition == null) {
throw new RuntimeException("Missing Bedrock ItemDefinition in mappings: " + bedrockIdentifier);
throw new RuntimeException("Missing Bedrock ItemDefinition in version " + palette.version() + " for mapping: " + mappingItem);
}
BlockDefinition bedrockBlock = null;
@ -430,7 +424,7 @@ public class ItemRegistryPopulator {
} else if (javaItem.javaIdentifier().startsWith("minecraft:music_disc_")) {
// The Java record level event uses the item ID as the "key" to play the record
Registries.RECORDS.register(javaItem.javaId(), SoundEvent.valueOf("RECORD_" +
javaItem.javaIdentifier().replace("minecraft:music_disc_", "").toUpperCase(Locale.ENGLISH)));
mapping.getBedrockIdentifier().replace("minecraft:music_disc_", "").toUpperCase(Locale.ENGLISH)));
}
mappings.add(mapping);
@ -521,7 +515,7 @@ public class ItemRegistryPopulator {
.customIdMappings(customIdMappings)
.build();
Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings);
Registries.ITEMS.register(palette.protocolVersion(), itemMappings);
firstMappingsPass = false;
}

View File

@ -26,12 +26,22 @@
package org.geysermc.geyser.registry.type;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.With;
/**
* Represents Geyser's own serialized item information before being processed per-version
*/
@Data
@ToString
@EqualsAndHashCode
@Getter
@With
@NoArgsConstructor
@AllArgsConstructor
public class GeyserMappingItem {
@JsonProperty("bedrock_identifier") String bedrockIdentifier;
@JsonProperty("bedrock_data") int bedrockData;

View File

@ -56,7 +56,11 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.Server
import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket;
import com.github.steveice10.packetlib.BuiltinFlags;
import com.github.steveice10.packetlib.Session;
import com.github.steveice10.packetlib.event.session.*;
import com.github.steveice10.packetlib.event.session.ConnectedEvent;
import com.github.steveice10.packetlib.event.session.DisconnectedEvent;
import com.github.steveice10.packetlib.event.session.PacketErrorEvent;
import com.github.steveice10.packetlib.event.session.PacketSendingEvent;
import com.github.steveice10.packetlib.event.session.SessionAdapter;
import com.github.steveice10.packetlib.packet.Packet;
import com.github.steveice10.packetlib.tcp.TcpClientSession;
import com.github.steveice10.packetlib.tcp.TcpSession;
@ -81,6 +85,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.value.qual.IntRange;
import org.cloudburstmc.math.vector.*;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
import org.cloudburstmc.protocol.bedrock.data.*;
import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData;
@ -93,7 +98,7 @@ import org.cloudburstmc.protocol.common.util.OptionalBoolean;
import org.geysermc.api.util.BedrockPlatform;
import org.geysermc.api.util.InputMode;
import org.geysermc.api.util.UiProfile;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.util.FormBuilder;
import org.geysermc.floodgate.crypto.FloodgateCipher;
@ -103,6 +108,7 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.entity.type.GeyserEntity;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
import org.geysermc.geyser.api.event.bedrock.SessionLoginEvent;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.network.RemoteServer;
import org.geysermc.geyser.command.GeyserCommandSource;
@ -123,6 +129,7 @@ import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.level.JavaDimension;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.level.physics.CollisionManager;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.network.netty.LocalSession;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.BlockMappings;
@ -877,6 +884,16 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
* After getting whatever credentials needed, we attempt to join the Java server.
*/
private void connectDownstream() {
SessionLoginEvent loginEvent = new SessionLoginEvent(this, remoteServer);
GeyserImpl.getInstance().eventBus().fire(loginEvent);
if (loginEvent.isCancelled()) {
String disconnectReason = loginEvent.disconnectReason() == null ?
BedrockDisconnectReasons.DISCONNECTED : loginEvent.disconnectReason();
disconnect(disconnectReason);
return;
}
this.remoteServer = loginEvent.remoteServer();
boolean floodgate = this.remoteServer.authType() == AuthType.FLOODGATE;
// Start ticking
@ -1539,6 +1556,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
startGamePacket.setRewindHistorySize(0);
startGamePacket.setServerAuthoritativeBlockBreaking(false);
if (GameProtocol.isPre1_20(this)) {
startGamePacket.getExperiments().add(new ExperimentData("next_major_update", true));
startGamePacket.getExperiments().add(new ExperimentData("sniffer", true));
}
upstream.sendPacket(startGamePacket);
}
@ -1942,8 +1964,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
}
EmotePacket packet = new EmotePacket();
packet.setEmoteId(emoteId);
packet.setRuntimeEntityId(entity.getGeyserId());
packet.setXuid("");
packet.setPlatformId(""); // BDS sends empty
packet.setEmoteId(emoteId);
sendUpstreamPacket(packet);
}

View File

@ -62,7 +62,7 @@ public class BookEditCache {
if ((System.currentTimeMillis() - lastBookUpdate) < 1000) {
return;
}
// Don't send the update if the player isn't not holding a book, shouldn't happen if we catch all interactions
// Don't send the update if the player is not holding a book, shouldn't happen if we catch all interactions
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand();
if (itemStack == null || itemStack.asItem() != Items.WRITABLE_BOOK) {
packet = null;

View File

@ -64,6 +64,7 @@ public class TagCache {
private IntList foxFood;
private IntList piglinLoved;
private IntList smallFlowers;
private IntList snifferFood;
public TagCache() {
// Ensure all lists are non-null
@ -101,6 +102,7 @@ public class TagCache {
this.foxFood = IntList.of(itemTags.get("minecraft:fox_food"));
this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved"));
this.smallFlowers = IntList.of(itemTags.get("minecraft:small_flowers"));
this.snifferFood = load(itemTags.get("minecraft:sniffer_food"));
// Hack btw
boolean emulatePost1_13Logic = itemTags.get("minecraft:signs").length > 1;
@ -137,6 +139,7 @@ public class TagCache {
this.foxFood = IntLists.emptyList();
this.piglinLoved = IntLists.emptyList();
this.smallFlowers = IntLists.emptyList();
this.snifferFood = IntLists.emptyList();
}
public boolean isAxolotlTemptItem(Item item) {
@ -167,6 +170,10 @@ public class TagCache {
return smallFlowers.contains(itemStack.getJavaId());
}
public boolean isSnifferFood(Item item) {
return snifferFood.contains(item.javaId());
}
public boolean isAxeEffective(BlockMapping blockMapping) {
return axeEffective.contains(blockMapping.getJavaBlockId());
}

View File

@ -61,6 +61,10 @@ public final class WorldCache {
private int currentSequence;
private final Object2IntMap<Vector3i> unverifiedPredictions = new Object2IntOpenHashMap<>(1);
@Getter
@Setter
private boolean editingSignOnFront;
public WorldCache(GeyserSession session) {
this.session = session;
this.scoreboard = new Scoreboard(session);

View File

@ -30,10 +30,9 @@ import org.geysermc.geyser.util.AssetUtils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.UUID;
@ -80,14 +79,14 @@ public final class ProvidedSkins {
.resolve(slim ? "slim" : "wide");
String assetName = asset.substring(asset.lastIndexOf('/') + 1);
File location = folder.resolve(assetName).toFile();
AssetUtils.addTask(!location.exists(), new AssetUtils.ClientJarTask("assets/minecraft/" + asset,
Path location = folder.resolve(assetName);
AssetUtils.addTask(!Files.exists(location), new AssetUtils.ClientJarTask("assets/minecraft/" + asset,
(stream) -> AssetUtils.saveFile(location, stream),
() -> {
try {
// TODO lazy initialize?
BufferedImage image;
try (InputStream stream = new FileInputStream(location)) {
try (InputStream stream = Files.newInputStream(location)) {
image = ImageIO.read(stream);
}

View File

@ -34,6 +34,7 @@ import org.geysermc.geyser.util.WebUtils;
import javax.annotation.Nullable;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
@ -57,8 +58,8 @@ public class MinecraftLocale {
}
public static void ensureEN_US() {
File localeFile = getFile("en_us");
AssetUtils.addTask(!localeFile.exists(), new AssetUtils.ClientJarTask("assets/minecraft/lang/en_us.json",
Path localeFile = getPath("en_us");
AssetUtils.addTask(!Files.exists(localeFile), new AssetUtils.ClientJarTask("assets/minecraft/lang/en_us.json",
(stream) -> AssetUtils.saveFile(localeFile, stream),
() -> {
if ("en_us".equals(GeyserLocale.getDefaultLocale())) {
@ -106,10 +107,10 @@ public class MinecraftLocale {
if (locale.equals("en_us")) {
return;
}
File localeFile = getFile(locale);
Path localeFile = getPath(locale);
// Check if we have already downloaded the locale file
if (localeFile.exists()) {
if (Files.exists(localeFile)) {
String curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile));
String targetHash = AssetUtils.getAsset("minecraft/lang/" + locale + ".json").getHash();
@ -130,8 +131,8 @@ public class MinecraftLocale {
}
}
private static File getFile(String locale) {
return GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile();
private static Path getPath(String locale) {
return GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json");
}
/**

View File

@ -94,7 +94,7 @@ public abstract class InventoryTranslator {
put(ContainerType.LOOM, new LoomInventoryTranslator());
put(ContainerType.MERCHANT, new MerchantInventoryTranslator());
put(ContainerType.SHULKER_BOX, new ShulkerInventoryTranslator());
put(ContainerType.LEGACY_SMITHING, new SmithingInventoryTranslator());
put(ContainerType.SMITHING, new SmithingInventoryTranslator());
put(ContainerType.STONECUTTER, new StonecutterInventoryTranslator());
/* Lectern */
@ -525,10 +525,28 @@ public abstract class InventoryTranslator {
int remainder = transferAction.getCount() % resultSize;
int timesToCraft = transferAction.getCount() / resultSize;
for (int i = 0; i < timesToCraft; i++) {
plan.add(Click.LEFT, sourceSlot);
plan.add(Click.LEFT, destSlot);
if (plan.getCursor().isEmpty()) {
// No carried items - move to destination
for (int i = 0; i < timesToCraft; i++) {
plan.add(Click.LEFT, sourceSlot);
plan.add(Click.LEFT, destSlot);
}
} else {
GeyserItemStack cursor = session.getPlayerInventory().getCursor();
int tempSlot = findTempSlot(inventory, cursor, true, sourceSlot, destSlot);
if (tempSlot == -1) {
return rejectRequest(request);
}
plan.add(Click.LEFT, tempSlot); //place cursor into temp slot
for (int i = 0; i < timesToCraft; i++) {
plan.add(Click.LEFT, sourceSlot); //pick up source item
plan.add(Click.LEFT, destSlot); //place source item into dest slot
}
plan.add(Click.LEFT, tempSlot); //pick up original item
}
if (remainder > 0) {
plan.add(Click.LEFT, 0);
for (int i = 0; i < remainder; i++) {

View File

@ -33,15 +33,16 @@ import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslator {
public SmithingInventoryTranslator() {
super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE);
super(4, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE);
}
@Override
public int bedrockSlotToJava(ItemStackRequestSlotData slotInfoData) {
return switch (slotInfoData.getContainer()) {
case SMITHING_TABLE_INPUT -> 0;
case SMITHING_TABLE_MATERIAL -> 1;
case SMITHING_TABLE_RESULT, CREATED_OUTPUT -> 2;
case SMITHING_TABLE_TEMPLATE -> 0;
case SMITHING_TABLE_INPUT -> 1;
case SMITHING_TABLE_MATERIAL -> 2;
case SMITHING_TABLE_RESULT, CREATED_OUTPUT -> 3;
default -> super.bedrockSlotToJava(slotInfoData);
};
}
@ -49,9 +50,10 @@ public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslato
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
return switch (slot) {
case 0 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
case 1 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
case 2 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
case 0 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_TEMPLATE, 53);
case 1 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
case 2 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
case 3 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
default -> super.javaSlotToBedrockContainer(slot);
};
}
@ -59,9 +61,10 @@ public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslato
@Override
public int javaSlotToBedrock(int slot) {
return switch (slot) {
case 0 -> 51;
case 1 -> 52;
case 2 -> 50;
case 0 -> 53;
case 1 -> 51;
case 2 -> 52;
case 3 -> 50;
default -> super.javaSlotToBedrock(slot);
};
}

View File

@ -62,19 +62,20 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl
}
StonecutterContainer container = (StonecutterContainer) inventory;
ItemStack javaOutput = craftingData.output();
int button = craftingData.buttonId();
// If we've already pressed the button with this item, no need to press it again!
if (container.getStonecutterButton() != button) {
ItemStack javaOutput = craftingData.output();
// Getting the index of the item in the Java stonecutter list
ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getJavaId(), button);
session.sendDownstreamPacket(packet);
container.setStonecutterButton(button);
if (inventory.getItem(1).getJavaId() != javaOutput.getId()) {
// We don't know there is an output here, so we tell ourselves that there is
inventory.setItem(1, GeyserItemStack.from(javaOutput), session);
}
}
if (inventory.getItem(1).getJavaId() != javaOutput.getId()) {
// We don't know there is an output here, so we tell ourselves that there is
inventory.setItem(1, GeyserItemStack.from(javaOutput), session);
}
return translateRequest(session, inventory, request);

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.translator.level.block.entity;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMapping;
@BlockEntity(type = BlockEntityType.BRUSHABLE_BLOCK)
public class BrushableBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
@Override
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
if (!(tag.remove("item") instanceof CompoundTag itemTag)) {
return;
}
Tag hitDirection = tag.get("hit_direction");
if (hitDirection == null) {
// java server sends no direction when the item recedes back into the block (if player stops brushing)
return;
}
String id = ((StringTag) itemTag.get("id")).getValue();
if (Items.AIR.javaIdentifier().equals(id)) {
return; // server sends air when the block contains nothing
}
ItemMapping mapping = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getMapping(id);
if (mapping == null) {
return;
}
NbtMapBuilder itemBuilder = NbtMap.builder()
.putString("Name", mapping.getBedrockIdentifier())
.putByte("Count", (byte) itemTag.get("Count").getValue());
builder.putCompound("item", itemBuilder.build());
// controls which side the item protrudes from
builder.putByte("brush_direction", ((Number) hitDirection.getValue()).byteValue());
// controls how much the item protrudes
builder.putInt("brush_count", BlockStateValues.getBrushProgress(blockState));
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.translator.level.block.entity;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType;
import java.util.ArrayList;
import java.util.List;
@BlockEntity(type = BlockEntityType.DECORATED_POT)
public class DecoratedPotBlockEntityTranslator extends BlockEntityTranslator {
@Override
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
// exact same format
if (tag.get("sherds") instanceof ListTag sherds) {
List<String> translated = new ArrayList<>(4);
for (Tag sherd : sherds) {
translated.add(((StringTag) sherd).getValue());
}
builder.putList("sherds", NbtType.STRING, translated);
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.translator.level.block.entity;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
import org.geysermc.geyser.util.SignUtils;
@BlockEntity(type = BlockEntityType.HANGING_SIGN)
public class HangingSignBlockEntityTranslator extends SignBlockEntityTranslator {
@Override
public int signWidthMax() {
return SignUtils.HANGING_SIGN_WIDTH_MAX; // Smaller than that for BlockEntityType.SIGN
}
}

View File

@ -36,13 +36,12 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.Getter;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.physics.Axis;
import org.geysermc.geyser.level.physics.BoundingBox;
import org.geysermc.geyser.level.physics.CollisionManager;
import org.geysermc.geyser.level.physics.Direction;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.PistonCache;
@ -622,10 +621,6 @@ public class PistonBlockEntity {
Vector3i movement = getMovement();
attachedBlocks.forEach((blockPos, javaId) -> {
blockPos = blockPos.add(movement);
if (!GameProtocol.supports1_19_50(session)) {
// Send a final block entity packet to detach blocks for clients older than 1.19.50
BlockEntityUtils.updateBlockEntity(session, buildMovingBlockTag(blockPos, javaId, Direction.DOWN.getUnitVector()), blockPos);
}
// Don't place blocks that collide with the player
if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) {
ChunkUtils.updateBlock(session, javaId, blockPos);

View File

@ -27,12 +27,15 @@ 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 com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.SignUtils;
@BlockEntity(type = {BlockEntityType.SIGN, BlockEntityType.HANGING_SIGN})
@BlockEntity(type = BlockEntityType.SIGN)
public class SignBlockEntityTranslator extends BlockEntityTranslator {
/**
* Maps a color stored in a sign's Color tag to its ARGB value.
@ -64,54 +67,80 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator {
return dyeColor | (255 << 24);
}
public int signWidthMax() {
return SignUtils.SIGN_WIDTH_MAX;
}
@Override
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
StringBuilder signText = new StringBuilder();
for (int i = 0; i < 4; i++) {
int currentLine = i + 1;
String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), "");
signLine = MessageTranslator.convertMessageLenient(signLine);
builder.putCompound("FrontText", translateSide(tag.get("front_text")));
builder.putCompound("BackText", translateSide(tag.get("back_text")));
var waxed = tag.get("is_waxed");
builder.putBoolean("IsWaxed", waxed != null && waxed.getValue() instanceof Number number && number.byteValue() != 0);
}
// Check the character width on the sign to ensure there is no overflow that is usually hidden
// to Java Edition clients but will appear to Bedrock clients
int signWidth = 0;
StringBuilder finalSignLine = new StringBuilder();
boolean previousCharacterWasFormatting = false; // Color changes do not count for maximum width
for (char c : signLine.toCharArray()) {
if (c == '\u00a7') {
// Don't count this character
previousCharacterWasFormatting = true;
} else if (previousCharacterWasFormatting) {
// Don't count this character either
previousCharacterWasFormatting = false;
} else {
signWidth += SignUtils.getCharacterWidth(c);
private NbtMap translateSide(Tag tag) {
if (!(tag instanceof CompoundTag signData)) {
return NbtMap.EMPTY;
}
NbtMapBuilder builder = NbtMap.builder();
StringBuilder signText = new StringBuilder();
Tag messages = signData.get("messages");
if (messages instanceof ListTag listTag) {
var it = listTag.iterator();
while (it.hasNext()) {
String signLine = (String) it.next().getValue();
signLine = MessageTranslator.convertMessageLenient(signLine);
// Check the character width on the sign to ensure there is no overflow that is usually hidden
// to Java Edition clients but will appear to Bedrock clients
int signWidth = 0;
StringBuilder finalSignLine = new StringBuilder();
boolean previousCharacterWasFormatting = false; // Color changes do not count for maximum width
for (char c : signLine.toCharArray()) {
if (c == ChatColor.ESCAPE) {
// Don't count this character
previousCharacterWasFormatting = true;
} else if (previousCharacterWasFormatting) {
// Don't count this character either
previousCharacterWasFormatting = false;
} else {
signWidth += SignUtils.getCharacterWidth(c);
}
if (signWidth <= signWidthMax()) {
finalSignLine.append(c);
} else {
// Adding the character would make Bedrock move to the next line - Java doesn't do that, so we do not want to
break;
}
}
// todo 1.20: update for hanging signs (smaller width). Currently OK because bedrock sees hanging signs as normal signs
if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) {
finalSignLine.append(c);
} else {
// Adding the character would make Bedrock move to the next line - Java doesn't do that, so we do not want to
break;
signText.append(finalSignLine);
if (it.hasNext()) {
signText.append("\n");
}
}
}
signText.append(finalSignLine);
signText.append("\n");
// Trim extra newlines - this makes editing difficult if preserved because the cursor starts at the bottom,
// Which can easily go over the screen
while (!signText.isEmpty() && signText.charAt(signText.length() - 1) == '\n') {
signText.deleteCharAt(signText.length() - 1);
}
builder.putString("Text", signText.toString());
// Java Edition 1.14 added the ability to change the text color of the whole sign using dye
Tag color = tag.get("Color");
Tag color = signData.get("color");
if (color != null) {
builder.putInt("SignTextColor", getBedrockSignColor(color.getValue().toString()));
}
// Glowing text
boolean isGlowing = getOrDefault(tag.getValue().get("GlowingText"), (byte) 0) != (byte) 0;
boolean isGlowing = getOrDefault(signData.get("has_glowing_text"), (byte) 0) != (byte) 0;
builder.putBoolean("IgnoreLighting", isGlowing);
builder.putBoolean("TextIgnoreLegacyBugResolved", isGlowing); // ??? required
return builder.build();
}
}

View File

@ -30,7 +30,6 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.Serverb
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@ -44,16 +43,12 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
public void translate(GeyserSession session, BlockEntityDataPacket packet) {
NbtMap tag = packet.getData();
String id = tag.getString("id");
if (id.equals("Sign")) {
String text;
if (GameProtocol.supports1_19_80(session)) {
// The other side is called... you guessed it... BackText
text = tag.getCompound("FrontText")
.getString("Text");
} else {
text = tag.getString("Text");
}
text = MessageTranslator.convertToPlainText(text);
if (id.endsWith("Sign")) {
// Hanging signs are narrower
int widthMax = SignUtils.getSignWidthMax(id.startsWith("Hanging"));
String text = MessageTranslator.convertToPlainText(
tag.getCompound(session.getWorldCache().isEditingSignOnFront() ? "FrontText" : "BackText").getString("Text"));
// Note: as of 1.18.30, only one packet is sent from Bedrock when the sign is finished.
// Previous versions did not have this behavior.
StringBuilder newMessage = new StringBuilder();
@ -68,13 +63,10 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
for (char character : text.toCharArray()) {
widthCount += SignUtils.getCharacterWidth(character);
// todo 1.20: update for hanging signs (smaller width). Currently bedrock thinks hanging signs are normal,
// so it thinks hanging signs have more width than they actually do. Seems like JE just truncates it.
// If we get a return in Bedrock, or go over the character width max, that signals to use the next line.
if (character == '\n' || widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX) {
if (character == '\n' || widthCount > widthMax) {
// We need to apply some more logic if we went over the character width max
boolean wentOverMax = widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX && character != '\n';
boolean wentOverMax = widthCount > widthMax && character != '\n';
widthCount = 0;
// Saves if we're moving a word to the next line
String word = null;
@ -115,7 +107,7 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
// Put the final line on since it isn't done in the for loop
if (iterator < lines.length) lines[iterator] = newMessage.toString();
Vector3i pos = Vector3i.from(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
ServerboundSignUpdatePacket signUpdatePacket = new ServerboundSignUpdatePacket(pos, lines);
ServerboundSignUpdatePacket signUpdatePacket = new ServerboundSignUpdatePacket(pos, lines, session.getWorldCache().isEditingSignOnFront());
session.sendDownstreamPacket(signUpdatePacket);
} else if (id.equals("JigsawBlock")) {

View File

@ -33,12 +33,12 @@ import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.cloudburstmc.protocol.bedrock.packet.BookEditPacket;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.type.WrittenBookItem;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.translator.text.MessageTranslator;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
@ -46,12 +46,10 @@ import java.util.List;
@Translator(packet = BookEditPacket.class)
public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket> {
private static final int MAXIMUM_PAGE_LENGTH = 8192 * 4;
private static final int MAXIMUM_TITLE_LENGTH = 128 * 4;
@Override
public void translate(GeyserSession session, BookEditPacket packet) {
if (packet.getText() != null && !packet.getText().isEmpty() && packet.getText().getBytes(StandardCharsets.UTF_8).length > MAXIMUM_PAGE_LENGTH) {
if (packet.getText() != null && !packet.getText().isEmpty() && packet.getText().length() > WrittenBookItem.MAXIMUM_PAGE_EDIT_LENGTH) {
session.getGeyser().getLogger().warning("Page length greater than server allowed!");
return;
}
@ -63,6 +61,10 @@ public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket>
List<Tag> pages = tag.contains("pages") ? new LinkedList<>(((ListTag) tag.get("pages")).getValue()) : new LinkedList<>();
int page = packet.getPageNumber();
if (page < 0 || WrittenBookItem.MAXIMUM_PAGE_COUNT <= page) {
session.getGeyser().getLogger().warning("Edited page is out of acceptable bounds!");
return;
}
switch (packet.getAction()) {
case ADD_PAGE: {
// Add empty pages in between
@ -129,7 +131,7 @@ public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket>
if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) {
// Add title to packet so the server knows we're signing
title = MessageTranslator.convertToPlainText(packet.getTitle());
if (title.getBytes(StandardCharsets.UTF_8).length > MAXIMUM_TITLE_LENGTH) {
if (title.length() > WrittenBookItem.MAXIMUM_TITLE_LENGTH) {
session.getGeyser().getLogger().warning("Book title larger than server allows!");
return;
}

View File

@ -26,10 +26,9 @@
package org.geysermc.geyser.translator.protocol.bedrock;
import org.cloudburstmc.protocol.bedrock.packet.CommandRequestPacket;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.translator.text.MessageTranslator;
@ -46,7 +45,9 @@ public class BedrockCommandRequestTranslator extends PacketTranslator<CommandReq
return;
}
session.sendCommand(command.substring(1));
// running commands via Bedrock's command select menu adds a trailing whitespace which Java doesn't like
// https://github.com/GeyserMC/Geyser/issues/3877
session.sendCommand(command.substring(1).stripTrailing());
}
}
}

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