diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 23e1576ae..5f4127b2b 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -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 }} diff --git a/README.md b/README.md index d951acee4..2faefed56 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java b/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java index f86206d36..de5c78678 100644 --- a/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java +++ b/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java @@ -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. * diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionJoinEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionJoinEvent.java new file mode 100644 index 000000000..ab2088c00 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionJoinEvent.java @@ -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); + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoadResourcePacksEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoadResourcePacksEvent.java new file mode 100644 index 000000000..c2f1cd427 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoadResourcePacksEvent.java @@ -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 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); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoginEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoginEvent.java new file mode 100644 index 000000000..c3c8198c1 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoginEvent.java @@ -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; + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java b/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java new file mode 100644 index 000000000..884129fa3 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java @@ -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); + } +} diff --git a/common/src/main/java/org/geysermc/common/PlatformType.java b/api/src/main/java/org/geysermc/geyser/api/pack/PathPackCodec.java similarity index 70% rename from common/src/main/java/org/geysermc/common/PlatformType.java rename to api/src/main/java/org/geysermc/geyser/api/pack/PathPackCodec.java index 667d49a7a..ee5db8242 100644 --- a/common/src/main/java/org/geysermc/common/PlatformType.java +++ b/api/src/main/java/org/geysermc/geyser/api/pack/PathPackCodec.java @@ -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(); } diff --git a/api/src/main/java/org/geysermc/geyser/api/pack/ResourcePack.java b/api/src/main/java/org/geysermc/geyser/api/pack/ResourcePack.java new file mode 100644 index 000000000..de1beaf65 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/pack/ResourcePack.java @@ -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 + *

+ * 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(); + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/pack/ResourcePackManifest.java b/api/src/main/java/org/geysermc/geyser/api/pack/ResourcePackManifest.java new file mode 100644 index 000000000..c9ccdd6c5 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/pack/ResourcePackManifest.java @@ -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 modules(); + + /** + * Gets the dependencies of the resource pack. + * + * @return the dependencies + */ + @NonNull + Collection 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(); + } +} + diff --git a/api/src/main/java/org/geysermc/geyser/api/util/PlatformType.java b/api/src/main/java/org/geysermc/geyser/api/util/PlatformType.java new file mode 100644 index 000000000..2244d3a2a --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/util/PlatformType.java @@ -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"); +} diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts index 3e0e9c147..4025569dd 100644 --- a/bootstrap/bungeecord/build.gradle.kts +++ b/bootstrap/bungeecord/build.gradle.kts @@ -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 { 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 { exclude(dependency("io.netty:netty-codec:.*")) exclude(dependency("io.netty:netty-resolver-dns:.*")) } -} \ No newline at end of file +} diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index 4141a8dbc..538e00a05 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -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; diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts index e85c2f809..44714cc7c 100644 --- a/bootstrap/fabric/build.gradle.kts +++ b/bootstrap/fabric/build.gradle.kts @@ -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") } -} \ No newline at end of file +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java index fdc820b19..64b050ec5 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java @@ -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 diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java index 454a9167e..9cd01f993 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java @@ -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 diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json index 98a410950..4c442017a 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/fabric/src/main/resources/fabric.mod.json @@ -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": "*" } } diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 58ea763eb..85a46598c 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -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 { 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 { // Commodore includes Brigadier exclude(dependency("com.mojang:.*")) } -} \ No newline at end of file +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index a660d735b..16ee13c82 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -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; diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java index dd84bf31c..a20f1617f 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java @@ -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; diff --git a/bootstrap/standalone/build.gradle.kts b/bootstrap/standalone/build.gradle.kts index b5fae9db3..eaf895108 100644 --- a/bootstrap/standalone/build.gradle.kts +++ b/bootstrap/standalone/build.gradle.kts @@ -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) diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java index c4271be4c..b505b361e 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java @@ -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); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java index e7e24a465..3c29bc648 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java @@ -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(); } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/ColorPane.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/ColorPane.java index c08f602d4..1bdc90123 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/ColorPane.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/ColorPane.java @@ -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 diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java index 41cbafb25..af3e1069f 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java @@ -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 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 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 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 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 + } + } } diff --git a/bootstrap/standalone/src/main/resources/log4j2.xml b/bootstrap/standalone/src/main/resources/log4j2.xml index 0738acdcd..54f6f9528 100644 --- a/bootstrap/standalone/src/main/resources/log4j2.xml +++ b/bootstrap/standalone/src/main/resources/log4j2.xml @@ -2,10 +2,10 @@ - + - + diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index 92b3a71a7..aac27fb65 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -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; diff --git a/build.gradle.kts b/build.gradle.kts index 4304811ff..c64667ad2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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 { diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 97540f96e..4d94479c3 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -30,7 +30,7 @@ dependencies { } implementation(libs.raknet) { - exclude("io.netty", "*"); + exclude("io.netty", "*") } implementation(libs.netty.resolver.dns) diff --git a/core/src/main/java/org/geysermc/connector/GeyserConnector.java b/core/src/main/java/org/geysermc/connector/GeyserConnector.java index 2616b91d3..381282a2a 100644 --- a/core/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/core/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -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; diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 8204cfd3b..d13836e9a 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -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> versionMap = new HashMap<>(); Map 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; diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index d28f9d24e..483e8d4a7 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -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; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java index 95c115769..8471fcd3f 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java @@ -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; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index 60683d34a..544be7446 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -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; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index 6e7ad2f04..d4ec7d161 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -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; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java index 843e93de0..8f147cdab 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java @@ -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; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java index 151aa2d84..7db539cc5 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java @@ -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; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index b2b5d54a3..378e43ee0 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -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; diff --git a/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java index fda0566fd..6a56c536a 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java @@ -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; diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java index 9c7e19853..a8d35b6f3 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java @@ -104,6 +104,7 @@ public final class EntityDefinitions { public static final EntityDefinition HORSE; public static final EntityDefinition HUSK; public static final EntityDefinition ILLUSIONER; // Not present on Bedrock + public static final EntityDefinition INTERACTION; public static final EntityDefinition IRON_GOLEM; public static final EntityDefinition ITEM; public static final EntityDefinition ITEM_FRAME; @@ -133,6 +134,7 @@ public final class EntityDefinitions { public static final EntityDefinition SALMON; public static final EntityDefinition SHEEP; public static final EntityDefinition SHULKER; + public static final EntityDefinition SNIFFER; public static final EntityDefinition SHULKER_BULLET; public static final EntityDefinition SILVERFISH; public static final EntityDefinition 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 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 diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java index 1bf6e581e..25569990e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java @@ -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; }); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index 6ada880ce..5ca739f61 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -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(); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/InteractionEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/InteractionEntity.java new file mode 100644 index 000000000..c88f90f19 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/InteractionEntity.java @@ -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(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java new file mode 100644 index 000000000..a97756e39 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java @@ -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 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); + } + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java index ed46cfc2a..4ce6f062b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java @@ -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); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index 279429242..540aa989f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -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 diff --git a/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java b/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java new file mode 100644 index 000000000..5ed0f8d22 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java @@ -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 packs; + + public SessionLoadResourcePacksEventImpl(GeyserSession session, Map packMap) { + super(session); + this.packs = packMap; + } + + public @NonNull Map getPacks() { + return packs; + } + + @Override + public @NonNull List 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; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java b/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java index f56b254a6..2764499ef 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java +++ b/core/src/main/java/org/geysermc/geyser/extension/event/GeyserExtensionEventBus.java @@ -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 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 Set> subscribers(@NonNull Class eventClass) { return eventBus.subscribers(eventClass); diff --git a/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java index 93c1917d2..105b5ca5b 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java @@ -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(); } } } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java index c4137fba9..42b9ae1a0 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java @@ -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 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 diff --git a/core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java new file mode 100644 index 000000000..584928e65 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java @@ -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 PATTERNS; + public static final List 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 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 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); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/Items.java b/core/src/main/java/org/geysermc/geyser/item/Items.java index e0e9a256b..303665c79 100644 --- a/core/src/main/java/org/geysermc/geyser/item/Items.java +++ b/core/src/main/java/org/geysermc/geyser/item/Items.java @@ -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 register(T item) { return register(item, Registries.JAVA_ITEMS.get().size()); diff --git a/core/src/main/java/org/geysermc/geyser/item/type/ArmorItem.java b/core/src/main/java/org/geysermc/geyser/item/type/ArmorItem.java index fc48c9f34..38144f318 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/ArmorItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/ArmorItem.java @@ -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; + } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java b/core/src/main/java/org/geysermc/geyser/item/type/ChestItem.java similarity index 54% rename from core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java rename to core/src/main/java/org/geysermc/geyser/item/type/ChestItem.java index a51e2307d..99857006c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/ChestItem.java @@ -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 - } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/DecoratedPotItem.java b/core/src/main/java/org/geysermc/geyser/item/type/DecoratedPotItem.java new file mode 100644 index 000000000..10d2a1bcc --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/type/DecoratedPotItem.java @@ -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); + } + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/type/Item.java b/core/src/main/java/org/geysermc/geyser/item/type/Item.java index af45959bf..eef02ff0e 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/Item.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/Item.java @@ -144,7 +144,7 @@ public class Item { } /** - * Takes NBT from Java Edition and converts any value that Bedrock parses differently.
+ * Takes NBT from Bedrock Edition and converts any value that Java parses differently.
* Do note that this method is, these days, only called in three places (as of 2023/~1.19): *