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 index de1beaf65..54971d402 100644 --- a/api/src/main/java/org/geysermc/geyser/api/pack/ResourcePack.java +++ b/api/src/main/java/org/geysermc/geyser/api/pack/ResourcePack.java @@ -26,12 +26,15 @@ package org.geysermc.geyser.api.pack; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * 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. + *

+ * Optionally, a content key and/or a subpack name to load can be provided. */ public interface ResourcePack { @@ -59,6 +62,25 @@ public interface ResourcePack { @NonNull String contentKey(); + /** + * Sets the content key of the resource pack. Lack of a content key can be represented by an empty string. + */ + void contentKey(@NonNull String contentKey); + + /** + * The subpack to tell Bedrock clients to load. Lack of a subpack to load is represented by an empty string. + * + * @return the subpack name, or an empty string if not set. + */ + @NonNull + String subpackName(); + + /** + * Sets the subpack name that clients should load. + * It must match one of the subpacks that can be found in the manifest. + */ + void subpackName(@Nullable String subpackName); + /** * Creates a resource pack with the given {@link PackCodec}. * 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 index c9ccdd6c5..06a1c5278 100644 --- a/api/src/main/java/org/geysermc/geyser/api/pack/ResourcePackManifest.java +++ b/api/src/main/java/org/geysermc/geyser/api/pack/ResourcePackManifest.java @@ -66,6 +66,14 @@ public interface ResourcePackManifest { @NonNull Collection dependencies(); + /** + * Gets the subpacks of the resource pack. + * + * @return the subpacks + */ + @NonNull + Collection subpacks(); + /** * Represents the header of a resource pack. */ @@ -172,6 +180,36 @@ public interface ResourcePackManifest { Version version(); } + /** + * Represents a subpack of a resource pack + */ + interface Subpack { + + /** + * Gets the folder name of the subpack. + * + * @return the folder name + */ + String folderName(); + + /** + * Gets the name of the subpack. + * It can be sent to the Bedrock client alongside the pack + * to load a particular subpack within a resource pack. + * + * @return the subpack name + */ + String name(); + + /** + * Gets the memory tier of the subpack. + * + * @return the memory tier + */ + + Float memoryTier(); + } + /** * Represents a version of a resource pack. */ diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index f56a8a43f..9d5724998 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -205,11 +205,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket(); for (ResourcePack pack : this.resourcePackLoadEvent.resourcePacks()) { - PackCodec codec = pack.codec(); ResourcePackManifest.Header header = pack.manifest().header(); resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry( - header.uuid().toString(), header.version().toString(), codec.size(), pack.contentKey(), - "", header.uuid().toString(), false, false)); + header.uuid().toString(), header.version().toString(), pack.codec().size(), pack.contentKey(), + pack.subpackName(), header.uuid().toString(), false, false)); } resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks()); session.sendUpstreamPacket(resourcePacksInfo); diff --git a/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java b/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java index 82408b6e7..64b9fdd81 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java +++ b/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java @@ -25,14 +25,62 @@ package org.geysermc.geyser.pack; +import lombok.RequiredArgsConstructor; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.pack.PackCodec; import org.geysermc.geyser.api.pack.ResourcePack; import org.geysermc.geyser.api.pack.ResourcePackManifest; -public record GeyserResourcePack(PackCodec codec, ResourcePackManifest manifest, String contentKey) implements ResourcePack { +@RequiredArgsConstructor +public class GeyserResourcePack implements ResourcePack { /** * The size of each chunk to use when sending the resource packs to clients in bytes */ public static final int CHUNK_SIZE = 102400; + + private final PackCodec codec; + private final ResourcePackManifest manifest; + private String contentKey; + private String subpackName; + + public GeyserResourcePack(PackCodec codec, ResourcePackManifest manifest, String contentKey) { + this(codec, manifest); + this.contentKey = contentKey; + } + + @Override + public @NonNull PackCodec codec() { + return this.codec; + } + + @Override + public @NonNull ResourcePackManifest manifest() { + return this.manifest; + } + + @Override + public @NonNull String contentKey() { + return this.contentKey == null ? "" : this.contentKey; + } + + @Override + public void contentKey(@Nullable String contentKey) { + this.contentKey = contentKey; + } + + @Override + public @NonNull String subpackName() { + return this.subpackName == null ? "" : this.subpackName; + } + + @Override + public void subpackName(@Nullable String subpackName) { + if (manifest.subpacks().stream().anyMatch(subpack -> subpack.name().equals(subpackName))) { + this.subpackName = subpackName; + } else { + throw new IllegalArgumentException("A subpack with the name '" + subpackName + "' does not exist!"); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePackManifest.java b/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePackManifest.java index 25a0f0ee0..1807c63bb 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePackManifest.java +++ b/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePackManifest.java @@ -37,7 +37,13 @@ import java.io.IOException; import java.util.Collection; import java.util.UUID; -public record GeyserResourcePackManifest(@JsonProperty("format_version") int formatVersion, Header header, Collection modules, Collection dependencies) implements ResourcePackManifest { +public record GeyserResourcePackManifest( + @JsonProperty("format_version") int formatVersion, + Header header, + Collection modules, + Collection dependencies, + Collection subpacks +) implements ResourcePackManifest { public record Header(UUID uuid, Version version, String name, String description, @JsonProperty("min_engine_version") Version minimumSupportedMinecraftVersion) implements ResourcePackManifest.Header { } @@ -45,6 +51,8 @@ public record GeyserResourcePackManifest(@JsonProperty("format_version") int for public record Dependency(UUID uuid, Version version) implements ResourcePackManifest.Dependency { } + public record Subpack(@JsonProperty("folder_name") String folderName, String name, @JsonProperty("memory_tier") Float memoryTier) implements ResourcePackManifest.Subpack { } + @JsonDeserialize(using = Version.VersionDeserializer.class) public record Version(int major, int minor, int patch) implements ResourcePackManifest.Version {