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 index c2f1cd427..ddb3a3205 100644 --- 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 @@ -29,7 +29,9 @@ 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 org.geysermc.geyser.api.pack.option.ResourcePackOption; +import java.util.Collection; import java.util.List; import java.util.UUID; @@ -57,6 +59,34 @@ public abstract class SessionLoadResourcePacksEvent extends ConnectionEvent { */ public abstract boolean register(@NonNull ResourcePack resourcePack); + /** + * Registers a {@link ResourcePack} to be sent to the client, but alongside + * specific options. + * + * @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, ResourcePackOption... resourcePackOptions); + + /** + * Returns the subpack options set for a specific resource pack. + * + * @param resourcePack the resourcePack for which the options are set + * @return a list of {@link ResourcePackOption} + */ + Collection options(ResourcePack resourcePack) { + return options(resourcePack.manifest().header().uuid()); + } + + /** + * Returns the subpack options set for a specific resource pack uuid. + * + * @param resourcePack the resourcePack for which the options are set + * @return a list of {@link ResourcePackOption} + */ + public abstract Collection options(UUID resourcePack); + /** * Unregisters a resource pack from being sent to the client. * @@ -64,4 +94,5 @@ public abstract class SessionLoadResourcePacksEvent extends ConnectionEvent { * @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/pack/PackCodec.java b/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java index 884129fa3..15d9220a9 100644 --- a/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java +++ b/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java @@ -53,13 +53,21 @@ public abstract class PackCodec { public abstract long size(); /** - * Serializes the given resource pack into a byte buffer. + * Use {@link #serialize()} instead. + */ + @Deprecated + @NonNull + public SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException { + return serialize(); + }; + + /** + * Serializes the given codec 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; + public abstract SeekableByteChannel serialize() throws IOException; /** * Creates a new resource pack from this codec. @@ -69,6 +77,13 @@ public abstract class PackCodec { @NonNull protected abstract ResourcePack create(); + /** + * Creates a new resource pack builder from this codec. + * + * @return the new resource pack builder + */ + protected abstract ResourcePack.@NonNull Builder createBuilder(); + /** * Creates a new pack provider from the given 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 index 54971d402..a92acccb3 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,7 +26,7 @@ package org.geysermc.geyser.api.pack; import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.GeyserApi; /** * Represents a resource pack sent to Bedrock clients @@ -63,23 +63,12 @@ public interface ResourcePack { 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. + * The default 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); + String defaultSubpackName(); /** * Creates a resource pack with the given {@link PackCodec}. @@ -91,4 +80,36 @@ public interface ResourcePack { static ResourcePack create(@NonNull PackCodec codec) { return codec.create(); } + + /** + * Returns a {@link Builder} for a resource pack. + * It can be used to set a content key, or a default subpack. + * + * @param codec the {@link PackCodec} to base the builder on + * @return a {@link Builder} to build a resource pack. + */ + static Builder builder(@NonNull PackCodec codec) { + return GeyserApi.api().provider(Builder.class, codec); + } + + /** + * A builder for a resource pack. It allows providing a content key manually, or + * setting a default subpack. + */ + interface Builder { + + ResourcePackManifest manifest(); + + PackCodec codec(); + + String contentKey(); + + String defaultSubpackName(); + + Builder contentKey(@NonNull String contentKey); + + Builder defaultSubpackName(@NonNull String subpackName); + + ResourcePack build(); + } } diff --git a/api/src/main/java/org/geysermc/geyser/api/pack/option/PriorityOption.java b/api/src/main/java/org/geysermc/geyser/api/pack/option/PriorityOption.java new file mode 100644 index 000000000..fcf5aa902 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/pack/option/PriorityOption.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.pack.option; + +import org.geysermc.geyser.api.GeyserApi; + +public interface PriorityOption extends ResourcePackOption { + + PriorityOption HIGH = PriorityOption.priority(10); + PriorityOption NORMAL = PriorityOption.priority(5); + PriorityOption LOW = PriorityOption.priority(0); + + int priority(); + + static PriorityOption priority(int priority) { + if (priority < 0 || priority > 10) { + throw new IllegalArgumentException("Priority must be between 0 and 10 inclusive!"); + } + return GeyserApi.api().provider(PriorityOption.class, priority); + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/pack/option/ResourcePackOption.java b/api/src/main/java/org/geysermc/geyser/api/pack/option/ResourcePackOption.java new file mode 100644 index 000000000..9776329e1 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/pack/option/ResourcePackOption.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.pack.option; + +import org.geysermc.geyser.api.pack.ResourcePack; + +/** + * Represents a resource pack option that can be used to specify how a resource + * pack is sent to Bedrock clients. + */ +public interface ResourcePackOption { + + Type type(); + + void validate(ResourcePack pack); + + enum Type { + SUBPACK("subpack"), + PRIORITY("priority"); + + Type(String name) { + } + } + +} diff --git a/api/src/main/java/org/geysermc/geyser/api/pack/option/SubpackOption.java b/api/src/main/java/org/geysermc/geyser/api/pack/option/SubpackOption.java new file mode 100644 index 000000000..8e1ec07b1 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/pack/option/SubpackOption.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.pack.option; + +import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.pack.ResourcePackManifest; + +/** + * Can be used to specify which subpack from a resource pack a player should load. + * Available subpacks can be seen in a resource pack manifest {@link ResourcePackManifest#subpacks()} + */ +public interface SubpackOption extends ResourcePackOption { + + /** + * Creates a subpack option based on a {@link ResourcePackManifest.Subpack} + * + * @param subpack the chosen subpack + * @return a subpack option specifying that subpack + */ + static SubpackOption subpack(ResourcePackManifest.Subpack subpack) { + return named(subpack.name()); + } + + /** + * Creates a subpack option based on a subpack name. + * + * @param subpackName the name of the subpack + * @return a subpack option specifying a subpack with that name + */ + static SubpackOption named(String subpackName) { + return GeyserApi.api().provider(SubpackOption.class, subpackName); + } + + /** + * @return the subpack name of the chosen subpack. + */ + String subpackName(); +} 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 index 5ed0f8d22..e31529fb7 100644 --- a/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java +++ b/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java @@ -25,11 +25,18 @@ package org.geysermc.geyser.event.type; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; 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.api.pack.option.PriorityOption; +import org.geysermc.geyser.api.pack.option.ResourcePackOption; +import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -37,10 +44,12 @@ import java.util.UUID; public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent { private final Map packs; + private final Map> options; - public SessionLoadResourcePacksEventImpl(GeyserSession session, Map packMap) { + public SessionLoadResourcePacksEventImpl(GeyserSession session) { super(session); - this.packs = packMap; + this.packs = new Object2ObjectLinkedOpenHashMap<>(Registries.RESOURCE_PACKS.get()); + this.options = new HashMap<>(); } public @NonNull Map getPacks() { @@ -54,16 +63,35 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE @Override public boolean register(@NonNull ResourcePack resourcePack) { + return register(resourcePack, PriorityOption.NORMAL); + } + + @Override + public boolean register(@NonNull ResourcePack resourcePack, ResourcePackOption... resourcePackOptions) { + for (ResourcePackOption option : resourcePackOptions) { + option.validate(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); + + String uuid = resourcePack.manifest().header().uuid().toString(); + packs.put(uuid, resourcePack); + options.put(uuid, List.of(resourcePackOptions)); return true; } + @Override + public Collection options(UUID resourcePack) { + Collection packOptions = options.get(resourcePack.toString()); + return packOptions == null ? List.of() : Collections.unmodifiableCollection(packOptions); + } + @Override public boolean unregister(@NonNull UUID uuid) { + options.remove(uuid.toString()); return packs.remove(uuid.toString()) != null; } } 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 9d5724998..19b27efa1 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -75,7 +75,6 @@ import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.util.ArrayDeque; import java.util.Deque; -import java.util.HashMap; import java.util.OptionalInt; public class UpstreamPacketHandler extends LoggingPacketHandler { @@ -200,7 +199,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { geyser.getSessionManager().addPendingSession(session); - this.resourcePackLoadEvent = new SessionLoadResourcePacksEventImpl(session, new HashMap<>(Registries.RESOURCE_PACKS.get())); + this.resourcePackLoadEvent = new SessionLoadResourcePacksEventImpl(session); this.geyser.eventBus().fire(this.resourcePackLoadEvent); ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket(); @@ -208,7 +207,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { ResourcePackManifest.Header header = pack.manifest().header(); resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry( header.uuid().toString(), header.version().toString(), pack.codec().size(), pack.contentKey(), - pack.subpackName(), header.uuid().toString(), false, false)); + pack.defaultSubpackName(), 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 64b9fdd81..b0464c71f 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java +++ b/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java @@ -25,62 +25,90 @@ 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; -@RequiredArgsConstructor -public class GeyserResourcePack implements ResourcePack { +public record GeyserResourcePack( + PackCodec codec, + ResourcePackManifest manifest, + String contentKey, + String defaultSubpackName +) 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; + this(codec, manifest, contentKey, ""); } - @Override - public @NonNull PackCodec codec() { - return this.codec; - } - @Override - public @NonNull ResourcePackManifest manifest() { - return this.manifest; - } + public static class Builder implements ResourcePack.Builder { - @Override - public @NonNull String contentKey() { - return this.contentKey == null ? "" : this.contentKey; - } + public Builder(PackCodec codec, ResourcePackManifest manifest) { + this.codec = codec; + this.manifest = manifest; + } - @Override - public void contentKey(@Nullable String contentKey) { - this.contentKey = contentKey; - } + public Builder(PackCodec codec, ResourcePackManifest manifest, String contentKey) { + this.codec = codec; + this.manifest = manifest; + this.contentKey = contentKey; + } - @Override - public @NonNull String subpackName() { - return this.subpackName == null ? "" : this.subpackName; - } + private final PackCodec codec; + private final ResourcePackManifest manifest; + private String contentKey; + private String defaultSubpackName; - @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!"); + @Override + public ResourcePackManifest manifest() { + return manifest; + } + + @Override + public PackCodec codec() { + return codec; + } + + @Override + public String contentKey() { + return this.contentKey == null ? "" : this.contentKey; + } + + @Override + public String defaultSubpackName() { + return this.defaultSubpackName == null ? "" : this.defaultSubpackName; + } + + public Builder contentKey(@Nullable String contentKey) { + this.contentKey = contentKey; + return this; + } + + public Builder defaultSubpackName(@Nullable String subpackName) { + if (manifest.subpacks().stream().anyMatch(subpack -> subpack.name().equals(subpackName))) { + this.defaultSubpackName = subpackName; + } else { + throw new IllegalArgumentException("A subpack with the name '" + subpackName + "' does not exist!"); + } + return this; + } + + public GeyserResourcePack build() { + if (contentKey == null) { + contentKey = ""; + } + if (defaultSubpackName == null) { + defaultSubpackName = ""; + } + + return new GeyserResourcePack(codec, manifest, contentKey, defaultSubpackName); } } + } diff --git a/core/src/main/java/org/geysermc/geyser/pack/option/GeyserPriorityOption.java b/core/src/main/java/org/geysermc/geyser/pack/option/GeyserPriorityOption.java new file mode 100644 index 000000000..201adca01 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/pack/option/GeyserPriorityOption.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.pack.option; + +import org.geysermc.geyser.api.pack.ResourcePack; +import org.geysermc.geyser.api.pack.option.PriorityOption; + +public class GeyserPriorityOption implements PriorityOption { + + private final int priority; + + public int priority() { + return priority; + } + + public GeyserPriorityOption(int priority) { + if (priority < 0 || priority > 10) { + throw new IllegalArgumentException("Priority must be between 0 and 10 inclusive!"); + } + this.priority = priority; + } + + @Override + public Type type() { + return Type.PRIORITY; + } + + @Override + public void validate(ResourcePack pack) { + if (priority <= 10 && priority > 0) { + throw new IllegalArgumentException("Priority must be between 0 and 10 inclusive!"); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/pack/option/GeyserSubpackOption.java b/core/src/main/java/org/geysermc/geyser/pack/option/GeyserSubpackOption.java new file mode 100644 index 000000000..c0513cfe3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/pack/option/GeyserSubpackOption.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.pack.option; + +import org.geysermc.geyser.api.pack.ResourcePack; +import org.geysermc.geyser.api.pack.ResourcePackManifest; +import org.geysermc.geyser.api.pack.option.SubpackOption; + +/** + * Can be used to specify which subpack from a resource pack a player should load. + * Available subpacks can be seen in a resource pack manifest {@link ResourcePackManifest#subpacks()} + */ +public class GeyserSubpackOption implements SubpackOption { + + private final String subpackName; + + public GeyserSubpackOption(String subpackName) { + this.subpackName = subpackName; + } + + @Override + public Type type() { + return Type.SUBPACK; + } + + @Override + public void validate(ResourcePack pack) { + if (pack.manifest().subpacks().stream().noneMatch(subpack -> subpack.name().equals(subpackName))) { + throw new IllegalArgumentException("No subpack with the name %s found!".formatted(subpackName)); + } + } + + @Override + public String subpackName() { + return subpackName; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/pack/path/GeyserPathPackCodec.java b/core/src/main/java/org/geysermc/geyser/pack/path/GeyserPathPackCodec.java index 84067600f..ea6f319f9 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/path/GeyserPathPackCodec.java +++ b/core/src/main/java/org/geysermc/geyser/pack/path/GeyserPathPackCodec.java @@ -79,15 +79,20 @@ public class GeyserPathPackCodec extends PathPackCodec { } @Override - public @NonNull SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException { + public @NonNull SeekableByteChannel serialize() throws IOException { return FileChannel.open(this.path); } @Override - protected @NonNull ResourcePack create() { + protected ResourcePack.@NonNull Builder createBuilder() { return ResourcePackLoader.readPack(this.path); } + @Override + protected @NonNull ResourcePack create() { + return createBuilder().build(); + } + private void checkLastModified() { try { FileTime lastModified = Files.getLastModifiedTime(this.path); diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 94de0c298..ac67a4717 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -40,10 +40,12 @@ import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.pack.PathPackCodec; -import org.geysermc.geyser.impl.camera.GeyserCameraFade; -import org.geysermc.geyser.impl.camera.GeyserCameraPosition; +import org.geysermc.geyser.api.pack.option.PriorityOption; +import org.geysermc.geyser.api.pack.option.SubpackOption; import org.geysermc.geyser.event.GeyserEventRegistrar; import org.geysermc.geyser.extension.command.GeyserExtensionCommand; +import org.geysermc.geyser.impl.camera.GeyserCameraFade; +import org.geysermc.geyser.impl.camera.GeyserCameraPosition; import org.geysermc.geyser.item.GeyserCustomItemData; import org.geysermc.geyser.item.GeyserCustomItemOptions; import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData; @@ -53,6 +55,8 @@ import org.geysermc.geyser.level.block.GeyserGeometryComponent; import org.geysermc.geyser.level.block.GeyserJavaBlockState; import org.geysermc.geyser.level.block.GeyserMaterialInstance; import org.geysermc.geyser.level.block.GeyserNonVanillaCustomBlockData; +import org.geysermc.geyser.pack.option.GeyserPriorityOption; +import org.geysermc.geyser.pack.option.GeyserSubpackOption; import org.geysermc.geyser.pack.path.GeyserPathPackCodec; import org.geysermc.geyser.registry.provider.ProviderSupplier; @@ -66,9 +70,10 @@ public class ProviderRegistryLoader implements RegistryLoader, Prov @Override public Map, ProviderSupplier> load(Map, ProviderSupplier> providers) { - // misc + // commands providers.put(Command.Builder.class, args -> new GeyserExtensionCommand.Builder<>((Extension) args[0])); + // custom blocks providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.Builder()); providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.Builder()); providers.put(JavaBlockState.Builder.class, args -> new GeyserJavaBlockState.Builder()); @@ -76,8 +81,13 @@ public class ProviderRegistryLoader implements RegistryLoader, Prov providers.put(MaterialInstance.Builder.class, args -> new GeyserMaterialInstance.Builder()); providers.put(GeometryComponent.Builder.class, args -> new GeyserGeometryComponent.Builder()); + // misc providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0])); + + // packs providers.put(PathPackCodec.class, args -> new GeyserPathPackCodec((Path) args[0])); + providers.put(PriorityOption.class, args -> new GeyserPriorityOption((int) args[0])); + providers.put(SubpackOption.class, args -> new GeyserSubpackOption((String) args[0])); // items providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.Builder()); diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ResourcePackLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ResourcePackLoader.java index 800a3d22c..98ec39375 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ResourcePackLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ResourcePackLoader.java @@ -99,7 +99,7 @@ public class ResourcePackLoader implements RegistryLoader