mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-12-19 14:59:27 +00:00
Add automatic loading of the GeyserOptionalPack (feature/configurate) (#5964)
* Add automatic downloading of the GeyserOptionalPack * Warn about including the OptionalPack from extensions when Geyser is already including it instead of throwing. * Copy optional pack instead of downloading --------- Co-authored-by: onebeastchris <github@onechris.mozmail.com>
This commit is contained in:
@@ -251,12 +251,10 @@ public interface GeyserConfig {
|
||||
String serverName();
|
||||
|
||||
@Comment("""
|
||||
Whether to automatically serve the GeyserOptionalPack to all connecting players.
|
||||
This adds some quality-of-life visual fixes for Bedrock players.
|
||||
See https://geysermc.org/wiki/other/geyseroptionalpack for all current features.
|
||||
Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players.
|
||||
If enabled, force-resource-packs will be enabled.""")
|
||||
@DefaultBoolean(true)
|
||||
boolean enableOptionalPack();
|
||||
boolean enableIntegratedPack();
|
||||
|
||||
@Comment("""
|
||||
Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat.
|
||||
@@ -439,6 +437,14 @@ public interface GeyserConfig {
|
||||
@DefaultBoolean(true)
|
||||
boolean addTeamSuggestions();
|
||||
|
||||
@Comment("""
|
||||
A list of remote resource pack urls to send to the Bedrock client for downloading.
|
||||
The Bedrock client is very picky about how these are delivered - please see our wiki page for further info: https://geysermc.org/wiki/geyser/packs/
|
||||
""")
|
||||
default List<String> resourcePackUrls() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Cannot be type File yet because we may want to hide it in plugin instances.
|
||||
@Comment("""
|
||||
Floodgate uses encryption to ensure use from authorized sources.
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.geysermc.geyser.api.pack.exception.ResourcePackException;
|
||||
import org.geysermc.geyser.api.pack.option.ResourcePackOption;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePack;
|
||||
import org.geysermc.geyser.pack.ResourcePackHolder;
|
||||
import org.geysermc.geyser.util.GeyserIntegratedPackUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -42,11 +43,12 @@ import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePacksEvent {
|
||||
public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePacksEvent implements GeyserIntegratedPackUtil {
|
||||
private final Map<UUID, ResourcePackHolder> packs;
|
||||
|
||||
public GeyserDefineResourcePacksEventImpl(Map<UUID, ResourcePackHolder> packMap) {
|
||||
this.packs = packMap;
|
||||
registerGeyserPack(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,6 +63,10 @@ public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePack
|
||||
throw new ResourcePackException(ResourcePackException.Cause.UNKNOWN_IMPLEMENTATION);
|
||||
}
|
||||
|
||||
if (handlePossibleOptionalPack(resourcePack)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uuid = resourcePack.uuid();
|
||||
if (packs.containsKey(uuid)) {
|
||||
throw new ResourcePackException(ResourcePackException.Cause.DUPLICATE);
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.geysermc.geyser.pack.ResourcePackHolder;
|
||||
import org.geysermc.geyser.pack.option.OptionHolder;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.GeyserIntegratedPackUtil;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
@@ -55,7 +56,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent {
|
||||
public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent implements GeyserIntegratedPackUtil {
|
||||
|
||||
/**
|
||||
* The packs for this Session. A {@link ResourcePackHolder} may contain resource pack options registered
|
||||
@@ -73,6 +74,8 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE
|
||||
|
||||
private final GeyserSession session;
|
||||
|
||||
private boolean willCdnFail = false;
|
||||
|
||||
public SessionLoadResourcePacksEventImpl(GeyserSession session) {
|
||||
super(session);
|
||||
this.session = session;
|
||||
@@ -88,6 +91,9 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE
|
||||
@Override
|
||||
public boolean register(@NonNull ResourcePack resourcePack) {
|
||||
try {
|
||||
if (handlePossibleOptionalPack(resourcePack)) {
|
||||
return false;
|
||||
}
|
||||
register(resourcePack, PriorityOption.NORMAL);
|
||||
} catch (ResourcePackException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("An exception occurred while registering resource pack: " + e.getMessage(), e);
|
||||
@@ -103,6 +109,10 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE
|
||||
throw new ResourcePackException(ResourcePackException.Cause.UNKNOWN_IMPLEMENTATION);
|
||||
}
|
||||
|
||||
if (handlePossibleOptionalPack(resourcePack)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uuid = resourcePack.uuid();
|
||||
if (packs.containsKey(uuid)) {
|
||||
throw new ResourcePackException(ResourcePackException.Cause.DUPLICATE);
|
||||
@@ -201,7 +211,14 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE
|
||||
public List<ResourcePacksInfoPacket.Entry> infoPacketEntries() {
|
||||
List<ResourcePacksInfoPacket.Entry> entries = new ArrayList<>();
|
||||
|
||||
boolean anyCdn = packs.values().stream().anyMatch(holder -> holder.codec() instanceof UrlPackCodec);
|
||||
boolean warned = false;
|
||||
|
||||
for (ResourcePackHolder holder : packs.values()) {
|
||||
if (!warned && anyCdn && !(holder.codec() instanceof UrlPackCodec)) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Mixing pack codecs will result in all UrlPackCodec delivered packs to fall back to non-cdn delivery!");
|
||||
warned = true;
|
||||
}
|
||||
GeyserResourcePack pack = holder.pack();
|
||||
ResourcePackManifest.Header header = pack.manifest().header();
|
||||
entries.add(new ResourcePacksInfoPacket.Entry(
|
||||
|
||||
@@ -236,15 +236,18 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
// Can happen if an error occurs in the resource pack event; that'll disconnect the player
|
||||
return PacketSignal.HANDLED;
|
||||
}
|
||||
session.includedPackActive(resourcePackLoadEvent.isIntegratedPackActive());
|
||||
|
||||
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
|
||||
resourcePacksInfo.getResourcePackInfos().addAll(this.resourcePackLoadEvent.infoPacketEntries());
|
||||
resourcePacksInfo.setVibrantVisualsForceDisabled(!session.isAllowVibrantVisuals());
|
||||
|
||||
resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().config().gameplay().forceResourcePacks() ||
|
||||
GeyserImpl.getInstance().config().gameplay().enableOptionalPack());
|
||||
resourcePackLoadEvent.isIntegratedPackActive());
|
||||
resourcePacksInfo.setWorldTemplateId(UUID.randomUUID());
|
||||
resourcePacksInfo.setWorldTemplateVersion("*");
|
||||
|
||||
GeyserImpl.getInstance().getLogger().info(resourcePacksInfo.toString());
|
||||
session.sendUpstreamPacket(resourcePacksInfo);
|
||||
|
||||
GeyserLocale.loadGeyserLocale(session.locale());
|
||||
|
||||
@@ -134,8 +134,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
|
||||
}
|
||||
|
||||
// Load all remote resource packs from the config before firing the new event
|
||||
// TODO configurate
|
||||
//packMap.putAll(loadRemotePacks());
|
||||
packMap.putAll(loadRemotePacks());
|
||||
|
||||
GeyserDefineResourcePacksEventImpl defineEvent = new GeyserDefineResourcePacksEventImpl(packMap);
|
||||
GeyserImpl.getInstance().eventBus().fire(defineEvent);
|
||||
@@ -239,8 +238,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
|
||||
}
|
||||
}
|
||||
|
||||
//List<String> remotePackUrls = instance.getConfig().getResourcePackUrls();
|
||||
List<String> remotePackUrls = List.of();
|
||||
List<String> remotePackUrls = instance.config().advanced().resourcePackUrls();
|
||||
Map<UUID, ResourcePackHolder> packMap = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
for (String url : remotePackUrls) {
|
||||
|
||||
@@ -748,6 +748,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
@Accessors(fluent = true)
|
||||
private boolean hasAcceptedCodeOfConduct = false;
|
||||
|
||||
@Accessors(fluent = true)
|
||||
@Setter
|
||||
private boolean includedPackActive = false;
|
||||
|
||||
private final Set<InputLocksFlag> inputLocksSet = EnumSet.noneOf(InputLocksFlag.class);
|
||||
private boolean inputLockDirty;
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.util;
|
||||
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.pack.PackCodec;
|
||||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.api.pack.UrlPackCodec;
|
||||
import org.geysermc.geyser.api.pack.option.PriorityOption;
|
||||
import org.geysermc.geyser.api.pack.option.ResourcePackOption;
|
||||
import org.geysermc.geyser.event.type.GeyserDefineResourcePacksEventImpl;
|
||||
import org.geysermc.geyser.pack.ResourcePackHolder;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public interface GeyserIntegratedPackUtil {
|
||||
|
||||
GeyserImpl instance = GeyserImpl.getInstance();
|
||||
UUID PACK_UUID = UUID.fromString("e5f5c938-a701-11eb-b2a3-047d7bb283ba");
|
||||
Path CACHE = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache");
|
||||
Path PACK_PATH = CACHE.resolve("GeyserIntegratedPack.mcpack");
|
||||
AtomicBoolean PACK_ENABLED = new AtomicBoolean(false);
|
||||
|
||||
default void registerGeyserPack(GeyserDefineResourcePacksEventImpl event) {
|
||||
if (!instance.config().gameplay().enableIntegratedPack()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pack = event.getPacks().get(PACK_UUID);
|
||||
if (pack != null) {
|
||||
if (pack.codec() instanceof UrlPackCodec) {
|
||||
// Must ensure correct place in pack stack
|
||||
pack.optionHolder().put(ResourcePackOption.Type.PRIORITY, PriorityOption.HIGH);
|
||||
instance.getLogger().info("Not adding our own copy of the integrated pack due to url pack codec presence!");
|
||||
PACK_ENABLED.set(true);
|
||||
return;
|
||||
}
|
||||
warnOptionalPackPresent(warnMessageLocation(pack.codec()));
|
||||
getPacks().remove(PACK_UUID);
|
||||
}
|
||||
|
||||
try {
|
||||
Files.createDirectories(CACHE);
|
||||
Files.copy(GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("GeyserIntegratedPack.mcpack"),
|
||||
PACK_PATH, StandardCopyOption.REPLACE_EXISTING);
|
||||
event.register(ResourcePack.create(PathPackCodec.path(PACK_PATH)), PriorityOption.HIGH);
|
||||
PACK_ENABLED.set(true);
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not copy over Geyser integrated resource pack!", e);
|
||||
}
|
||||
}
|
||||
|
||||
default boolean handlePossibleOptionalPack(ResourcePack pack) {
|
||||
if (!PACK_ENABLED.get()) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(pack.uuid(), PACK_UUID)) {
|
||||
return false;
|
||||
}
|
||||
if (pack.codec() instanceof UrlPackCodec) {
|
||||
getPacks().remove(PACK_UUID);
|
||||
instance.getLogger().info("Overriding our own integrated pack with url pack codec delivered pack!");
|
||||
return false;
|
||||
}
|
||||
warnOptionalPackPresent(warnMessageLocation(pack.codec()));
|
||||
return true;
|
||||
}
|
||||
|
||||
Map<UUID, ResourcePackHolder> getPacks();
|
||||
|
||||
default String warnMessageLocation(PackCodec codec) {
|
||||
if (codec instanceof PathPackCodec pathPackCodec) {
|
||||
return "(found in: %s)".formatted(instance.getBootstrap().getConfigFolder().relativize(pathPackCodec.path()));
|
||||
}
|
||||
return "(registered with codec: %s)".formatted(codec);
|
||||
}
|
||||
|
||||
default void warnOptionalPackPresent(String message) {
|
||||
instance.getLogger().warning("Detected duplicate GeyserOptionalPack registration! " +
|
||||
" It should be removed " + message + ", as Geyser now includes an improved version of this resource pack by default!"
|
||||
);
|
||||
}
|
||||
|
||||
default boolean isIntegratedPackActive() {
|
||||
return instance.config().gameplay().enableIntegratedPack() && getPacks().containsKey(PACK_UUID);
|
||||
}
|
||||
}
|
||||
@@ -109,14 +109,24 @@ public class WebUtils {
|
||||
* @param fileLocation Location to save on disk
|
||||
*/
|
||||
public static void downloadFile(String reqURL, String fileLocation) {
|
||||
downloadFile(reqURL, Paths.get(fileLocation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a file from the given URL and saves it to disk
|
||||
*
|
||||
* @param reqURL File to fetch
|
||||
* @param path Location to save on disk as a path
|
||||
*/
|
||||
public static void downloadFile(String reqURL, Path path) {
|
||||
try {
|
||||
HttpURLConnection con = (HttpURLConnection) new URL(reqURL).openConnection();
|
||||
con.setRequestProperty("User-Agent", getUserAgent());
|
||||
checkResponseCode(con);
|
||||
InputStream in = con.getInputStream();
|
||||
Files.copy(in, Paths.get(fileLocation), StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to download and save file: " + fileLocation + " (" + reqURL + ")", e);
|
||||
throw new RuntimeException("Unable to download and save file: " + path.toAbsolutePath() + " (" + reqURL + ")", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BIN
core/src/main/resources/GeyserIntegratedPack.mcpack
Normal file
BIN
core/src/main/resources/GeyserIntegratedPack.mcpack
Normal file
Binary file not shown.
Reference in New Issue
Block a user