From 9007ee46306a8cbe073257b75e13b9791895428e Mon Sep 17 00:00:00 2001 From: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> Date: Sat, 21 Jan 2023 22:44:47 -0800 Subject: [PATCH] API event for skull blocks & let register via URL Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> --- .../GeyserDefineCustomBlocksEvent.java | 2 +- .../GeyserDefineCustomSkullsEvent.java | 28 ++++ .../GeyserCustomSkullConfiguration.java | 15 +- .../CustomSkullRegistryPopulator.java | 140 +++++++++++++----- core/src/main/resources/config.yml | 10 -- core/src/main/resources/custom-skulls.yml | 6 +- 6 files changed, 144 insertions(+), 57 deletions(-) create mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomSkullsEvent.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomBlocksEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomBlocksEvent.java index c821c593a..e95117105 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomBlocksEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomBlocksEvent.java @@ -33,7 +33,7 @@ import org.geysermc.event.Event; /** * Called on Geyser's startup when looking for custom blocks. Custom blocks must be registered through this event. * - * This event will not be called if the "add-custom-blocks" setting is disabled in the Geyser config. + * This event will not be called if the "add-non-bedrock-items" setting is disabled in the Geyser config. */ public abstract class GeyserDefineCustomBlocksEvent implements Event { diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomSkullsEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomSkullsEvent.java new file mode 100644 index 000000000..e6e48d9d4 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomSkullsEvent.java @@ -0,0 +1,28 @@ +package org.geysermc.geyser.api.event.lifecycle; + +import lombok.NonNull; +import org.geysermc.event.Event; + +/** + * Called on Geyser's startup when looking for custom skulls. Custom skulls must be registered through this event. + * + * This event will not be called if the "add-non-bedrock-items" setting is disabled in the Geyser config. + */ +public abstract class GeyserDefineCustomSkullsEvent implements Event { + /** + * The type of texture provided + */ + public enum SkullTextureType { + USERNAME, + UUID, + PROFILE, + SKIN_URL + } + + /** + * Registers the given username, UUID, or base64 encoded profile as a custom skull blocks + * @param texture the username, UUID, or base64 encoded profile + * @param type the type of texture provided + */ + public abstract void registerCustomSkull(@NonNull String texture, @NonNull SkullTextureType type); +} diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserCustomSkullConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserCustomSkullConfiguration.java index 497fb37ed..6199dfcb8 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserCustomSkullConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserCustomSkullConfiguration.java @@ -41,8 +41,11 @@ public class GeyserCustomSkullConfiguration { @JsonProperty("player-uuids") private List playerUUIDs; - @JsonProperty("textures") - private List textures; + @JsonProperty("player-profiles") + private List playerProfiles; + + @JsonProperty("skin-urls") + private List skinUrls; public List getPlayerUsernames() { return Objects.requireNonNullElse(playerUsernames, Collections.emptyList()); @@ -52,7 +55,11 @@ public class GeyserCustomSkullConfiguration { return Objects.requireNonNullElse(playerUUIDs, Collections.emptyList()); } - public List getTextures() { - return Objects.requireNonNullElse(textures, Collections.emptyList()); + public List getPlayerProfiles() { + return Objects.requireNonNullElse(playerProfiles, Collections.emptyList()); + } + + public List getPlayerSkinUrls() { + return Objects.requireNonNullElse(skinUrls, Collections.emptyList()); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java index bb2afce5b..89bbb46a3 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java @@ -27,8 +27,10 @@ package org.geysermc.geyser.registry.populator; import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.NonNull; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomSkullsEvent; import org.geysermc.geyser.configuration.GeyserCustomSkullConfiguration; import org.geysermc.geyser.pack.SkullResourcePackManager; import org.geysermc.geyser.registry.BlockRegistries; @@ -69,58 +71,114 @@ public class CustomSkullRegistryPopulator { BlockRegistries.CUSTOM_SKULLS.set(new Object2ObjectOpenHashMap<>()); - List textures = new ArrayList<>(skullConfig.getTextures()); - // TODO see if this can be cleaned up any better - for (String username : skullConfig.getPlayerUsernames()) { - try { - String texture = SkinProvider.requestTexturesFromUsername(username).get(); - if (texture == null) { - GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + username + " This skull will not be added as a custom block."); - continue; + List profiles = new ArrayList<>(skullConfig.getPlayerProfiles()); + + List usernames = new ArrayList<>(skullConfig.getPlayerUsernames()); + + List uuids = new ArrayList<>(skullConfig.getPlayerUUIDs()); + + List skinUrls = new ArrayList<>(skullConfig.getPlayerSkinUrls()); + + GeyserImpl.getInstance().getEventBus().fire(new GeyserDefineCustomSkullsEvent() { + @Override + public void registerCustomSkull(@NonNull String texture, @NonNull SkullTextureType type) { + switch (type) { + case USERNAME -> usernames.add(texture); + case UUID -> uuids.add(texture); + case PROFILE -> profiles.add(texture); + case SKIN_URL -> skinUrls.add(texture); + } + } + }); + + for (String username : usernames) { + String profile = getProfileFromUsername(username); + if (profile != null) { + String skinUrl = getSkinUrl(profile); + if (skinUrl != null) { + skinUrls.add(skinUrl); } - textures.add(texture); - } catch (InterruptedException | ExecutionException e) { - GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + username + " This skull will not be added as a custom block.", e); } } - for (String uuid : skullConfig.getPlayerUUIDs()) { - try { - String uuidDigits = uuid.replace("-", ""); - if (uuidDigits.length() != 32) { - GeyserImpl.getInstance().getLogger().error("Invalid skull uuid " + uuid + " This skull will not be added as a custom block."); - continue; + for (String uuid : uuids) { + String profile = getProfileFromUuid(uuid); + if (profile != null) { + String skinUrl = getSkinUrl(profile); + if (skinUrl != null) { + skinUrls.add(skinUrl); } - String texture = SkinProvider.requestTexturesFromUUID(uuid).get(); - if (texture == null) { - GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + uuid + " This skull will not be added as a custom block."); - continue; - } - textures.add(texture); - } catch (InterruptedException | ExecutionException e) { - GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + uuid + " This skull will not be added as a custom block.", e); } } - for (String texture : textures) { + for (String profile : profiles) { + String skinUrl = getSkinUrl(profile); + if (skinUrl != null) { + skinUrls.add(skinUrl); + } + } + + for (String skinUrl : skinUrls) { try { - SkinManager.GameProfileData profileData = SkinManager.GameProfileData.loadFromJson(texture); - if (profileData == null) { - GeyserImpl.getInstance().getLogger().warning("Skull texture " + texture + " contained no skins and will not be added as a custom block."); - continue; - } - try { - String skinUrl = profileData.skinUrl(); - String skinHash = skinUrl.substring(skinUrl.lastIndexOf("/") + 1); - SkullResourcePackManager.cacheSkullSkin(skinUrl, skinHash); - BlockRegistries.CUSTOM_SKULLS.register(skinHash, new CustomSkull(skinHash)); - } catch (IOException e) { - GeyserImpl.getInstance().getLogger().error("Failed to cache skin for skull texture " + texture + " This skull will not be added as a custom block.", e); - } + String skinHash = skinUrl.substring(skinUrl.lastIndexOf("/") + 1); + SkullResourcePackManager.cacheSkullSkin(skinUrl, skinHash); + BlockRegistries.CUSTOM_SKULLS.register(skinHash, new CustomSkull(skinHash)); } catch (IOException e) { - GeyserImpl.getInstance().getLogger().error("Skull texture " + texture + " is invalid and will not be added as a custom block.", e); + GeyserImpl.getInstance().getLogger().error("Failed to cache skin for skull texture " + skinUrl + " This skull will not be added as a custom block.", e); } } - GeyserImpl.getInstance().getLogger().debug("Registered " + BlockRegistries.CUSTOM_SKULLS.get().size() + " custom skulls as custom blocks."); + GeyserImpl.getInstance().getLogger().info("Registered " + BlockRegistries.CUSTOM_SKULLS.get().size() + " custom skulls as custom blocks."); + } + + /** + * Gets the skin URL from a base64 encoded profile + * @param profile the base64 encoded profile + * @return the skin URL or null if the profile is invalid + */ + private static String getSkinUrl(String profile) { + try { + SkinManager.GameProfileData profileData = SkinManager.GameProfileData.loadFromJson(profile); + if (profileData == null) { + GeyserImpl.getInstance().getLogger().warning("Skull texture " + profile + " contained no skins and will not be added as a custom block."); + return null; + } + return profileData.skinUrl(); + } catch (IOException e) { + GeyserImpl.getInstance().getLogger().error("Skull texture " + profile + " is invalid and will not be added as a custom block.", e); + return null; + } + } + + /** + * Gets the base64 encoded profile from a player's username + * @param username the player username + * @return the base64 encoded profile or null if the request failed + */ + private static String getProfileFromUsername(String username) { + try { + return SkinProvider.requestTexturesFromUsername(username).get(); + } catch (InterruptedException | ExecutionException e) { + GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + username + " This skull will not be added as a custom block.", e); + return null; + } + } + + /** + * Gets the base64 encoded profile from a player's UUID + * @param uuid the player UUID + * @return the base64 encoded profile or null if the request failed + */ + private static String getProfileFromUuid(String uuid) { + try { + String uuidDigits = uuid.replace("-", ""); + if (uuidDigits.length() != 32) { + GeyserImpl.getInstance().getLogger().error("Invalid skull uuid " + uuid + " This skull will not be added as a custom block."); + return null; + } + return SkinProvider.requestTexturesFromUUID(uuid).get(); + } catch (InterruptedException | ExecutionException e) { + GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + uuid + " This skull will not be added as a custom block.", e); + return null; + } } } diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 46ad9f733..94a7a7f08 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -165,16 +165,6 @@ custom-skull-render-distance: 32 # This option requires a restart of Geyser in order to change its setting. add-non-bedrock-items: true -# Whether to allow custom blocks to be added. -# This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. -add-custom-blocks: true - -# Whether to allow some custom skulls to be translated as custom blocks and displayed in the inventory and on entities. -# This requires `add-custom-blocks` and `allow-custom-skulls` to be enabled. -# This will generate a resource pack for Bedrock players and enables `force-resource-packs`. -# Custom skulls can be added in `custom-skulls.yml` -add-custom-skull-blocks: false - # Bedrock prevents building and displaying blocks above Y127 in the Nether. # This config option works around that by changing the Nether dimension ID to the End ID. # The main downside to this is that the entire Nether will have the same red fog rather than having different fog for each biome. diff --git a/core/src/main/resources/custom-skulls.yml b/core/src/main/resources/custom-skulls.yml index 99797b380..4f5e2ce1b 100644 --- a/core/src/main/resources/custom-skulls.yml +++ b/core/src/main/resources/custom-skulls.yml @@ -21,5 +21,9 @@ player-uuids: # - 8b8d8e8f-2759-47c6-acb5-5827de8a72b8 # The long string of characters found in the NBT of custom player heads -textures: +player-profiles: # - ewogICJ0aW1lc3RhbXAiIDogMTY1NzMyMjIzOTgzMywKICAicHJvZmlsZUlkIiA6ICJjZGRiZTUyMGQwNDM0YThiYTFjYzlmYzkyZmRlMmJjZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJBbWJlcmljaHUiLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTkwNzkwYzU3ZTE4MWVkMTNhZGVkMTRjNDdlZTJmN2M4ZGUzNTMzZTAxN2JhOTU3YWY3YmRmOWRmMWJkZTk0ZiIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9 + +# The URL of the skin on Minecraft's skin server +skin-urls: +# - http://textures.minecraft.net/texture/a90790c57e181ed13aded14c47ee2f7c8de3533e017ba957af7bdf9df1bde94f \ No newline at end of file